@ia-ccun/code-agent-cli 0.0.52 → 0.0.53-beta.2
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/bin/cli.js +97 -69
- package/config/agent/bin/README.md +102 -0
- package/config/agent/extensions/aicode-advisor.ts +197 -61
- package/config/agent/extensions/aicode-banner.ts +101 -0
- package/config/agent/extensions/aicode-btw.ts +677 -183
- package/config/agent/extensions/aicode-cd.ts +1017 -0
- package/config/agent/extensions/aicode-grillMe.ts +198 -0
- package/config/agent/extensions/aicode-pets.ts +351 -0
- package/config/agent/extensions/aicode-plan.ts +2 -0
- package/config/agent/extensions/aicode-safety-guard.ts +555 -0
- package/config/agent/extensions/aicode-tips.ts +69 -30
- package/config/agent/extensions/aicode-todo-progress.ts +547 -0
- package/config/agent/extensions/context-monitor.ts +30 -15
- package/config/agent/extensions/context.ts +132 -60
- package/config/agent/extensions/custom-footer.ts +0 -5
- package/config/agent/extensions/custom.ts +119 -109
- package/config/agent/extensions/error-monitor.ts +38 -51
- package/config/agent/extensions/goal.ts +8 -6
- package/config/agent/extensions/msg-debug.ts +103 -112
- package/config/agent/extensions/ponytail.ts +441 -0
- package/config/agent/extensions/review.ts +365 -164
- package/config/agent/extensions/tps.ts +87 -0
- package/config/agent/extensions/working-msg.ts +164 -51
- package/config/agent/help.md +0 -7
- package/config/agent/models.json +7 -7
- package/config/agent/settings.json +2 -1
- package/config/agent/skills/chroma/LICENSE +21 -0
- package/config/agent/skills/chroma/chroma-local/SKILL.md +165 -0
- package/config/agent/skills/chroma/chroma-local/cli.md +48 -0
- package/config/agent/skills/chroma/chroma-local/data-model.md +103 -0
- package/config/agent/skills/chroma/chroma-local/error-handling/python.md +166 -0
- package/config/agent/skills/chroma/chroma-local/error-handling/typescript.md +231 -0
- package/config/agent/skills/chroma/chroma-local/local-chroma/python.md +95 -0
- package/config/agent/skills/chroma/chroma-local/local-chroma/typescript.md +101 -0
- package/config/agent/skills/chroma/chroma-local/metadata/python.md +154 -0
- package/config/agent/skills/chroma/chroma-local/metadata/typescript.md +153 -0
- package/config/agent/skills/chroma/chroma-local/querying/python.md +147 -0
- package/config/agent/skills/chroma/chroma-local/querying/typescript.md +157 -0
- package/config/agent/skills/chroma/chroma-local/regex/python.md +69 -0
- package/config/agent/skills/chroma/chroma-local/regex/typescript.md +71 -0
- package/config/agent/skills/chroma/chroma-local/understanding-a-codebase.md +80 -0
- package/config/agent/skills/chroma/chroma-local/updating-deleting/python.md +159 -0
- package/config/agent/skills/chroma/chroma-local/updating-deleting/typescript.md +228 -0
- package/config/agent/skills/milvus-skill/LICENSE +202 -0
- package/config/agent/skills/milvus-skill/README.md +37 -0
- package/config/agent/skills/milvus-skill/SKILL.md +259 -0
- package/config/agent/skills/milvus-skill/references/collection.md +86 -0
- package/config/agent/skills/milvus-skill/references/database.md +24 -0
- package/config/agent/skills/milvus-skill/references/index.md +69 -0
- package/config/agent/skills/milvus-skill/references/partition.md +29 -0
- package/config/agent/skills/milvus-skill/references/patterns.md +150 -0
- package/config/agent/skills/milvus-skill/references/user-role.md +90 -0
- package/config/agent/skills/milvus-skill/references/vector.md +247 -0
- package/config/agent/skills/ponytail/SKILL.md +100 -0
- package/config/agent/skills/ponytail-audit/SKILL.md +40 -0
- package/config/agent/skills/ponytail-debt/SKILL.md +44 -0
- package/config/agent/skills/ponytail-help/SKILL.md +68 -0
- package/config/agent/skills/ponytail-review/SKILL.md +56 -0
- package/config/agent/skills/zread/README.md +115 -0
- package/config/agent/skills/zread/SKILL.md +103 -0
- package/config/agent/skills/zread/references/stdio-protocol.md +312 -0
- package/dist/banner.d.ts.map +1 -1
- package/dist/banner.js +8 -1
- package/dist/banner.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +9 -2
- package/dist/config-loader.js.map +1 -1
- package/package.json +7 -7
- package/scripts/postinstall.js +58 -1
- package/scripts/regression/README.md +224 -0
- package/scripts/regression/cases/01-version-and-deps.sh +94 -0
- package/scripts/regression/cases/02-extension-loading.sh +130 -0
- package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
- package/scripts/regression/cases/04-prompt-stream.sh +130 -0
- package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
- package/scripts/regression/cases/06-tool-execution.sh +115 -0
- package/scripts/regression/cases/07-pi-new-features.sh +141 -0
- package/scripts/regression/lib/common.sh +287 -0
- package/scripts/regression/results/.last_pid +1 -0
- package/scripts/regression/results/.last_workdir +1 -0
- package/scripts/regression/results/02-extension-loading.log +18 -0
- package/scripts/regression/results/03-prompt-stream.log +16 -0
- package/scripts/regression/results/03-rpc-protocol.log +27 -0
- package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
- package/scripts/regression/results/05-tool-execution.log +8 -0
- package/scripts/regression/results/06-pi-new-features.log +29 -0
- package/scripts/regression/run-all.sh +187 -0
- package/config/agent/dangerous-patterns.json +0 -106
- package/config/agent/extensions/bash-check.ts +0 -227
|
@@ -9,9 +9,21 @@
|
|
|
9
9
|
* - current context window usage + session totals (tokens/cost)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
ExtensionAPI,
|
|
14
|
+
ExtensionCommandContext,
|
|
15
|
+
ExtensionContext,
|
|
16
|
+
ToolResultEvent,
|
|
17
|
+
} from "@earendil-works/pi-coding-agent";
|
|
13
18
|
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
Container,
|
|
21
|
+
Key,
|
|
22
|
+
Text,
|
|
23
|
+
matchesKey,
|
|
24
|
+
type Component,
|
|
25
|
+
type TUI,
|
|
26
|
+
} from "@earendil-works/pi-tui";
|
|
15
27
|
import os from "node:os";
|
|
16
28
|
import path from "node:path";
|
|
17
29
|
import fs from "node:fs/promises";
|
|
@@ -66,17 +78,25 @@ function getAgentDir(): string {
|
|
|
66
78
|
return path.join(os.homedir(), ".pi", "agent");
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
async function readFileIfExists(
|
|
81
|
+
async function readFileIfExists(
|
|
82
|
+
filePath: string,
|
|
83
|
+
): Promise<{ path: string; content: string; bytes: number } | null> {
|
|
70
84
|
if (!existsSync(filePath)) return null;
|
|
71
85
|
try {
|
|
72
86
|
const buf = await fs.readFile(filePath);
|
|
73
|
-
return {
|
|
87
|
+
return {
|
|
88
|
+
path: filePath,
|
|
89
|
+
content: buf.toString("utf8"),
|
|
90
|
+
bytes: buf.byteLength,
|
|
91
|
+
};
|
|
74
92
|
} catch {
|
|
75
93
|
return null;
|
|
76
94
|
}
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
async function loadProjectContextFiles(
|
|
97
|
+
async function loadProjectContextFiles(
|
|
98
|
+
cwd: string,
|
|
99
|
+
): Promise<Array<{ path: string; tokens: number; bytes: number }>> {
|
|
80
100
|
const out: Array<{ path: string; tokens: number; bytes: number }> = [];
|
|
81
101
|
const seen = new Set<string>();
|
|
82
102
|
|
|
@@ -86,7 +106,11 @@ async function loadProjectContextFiles(cwd: string): Promise<Array<{ path: strin
|
|
|
86
106
|
const f = await readFileIfExists(p);
|
|
87
107
|
if (f && !seen.has(f.path)) {
|
|
88
108
|
seen.add(f.path);
|
|
89
|
-
out.push({
|
|
109
|
+
out.push({
|
|
110
|
+
path: f.path,
|
|
111
|
+
tokens: estimateTokens(f.content),
|
|
112
|
+
bytes: f.bytes,
|
|
113
|
+
});
|
|
90
114
|
// pi loads at most one of those per dir
|
|
91
115
|
return;
|
|
92
116
|
}
|
|
@@ -125,7 +149,8 @@ function buildSkillIndex(pi: ExtensionAPI, cwd: string): SkillIndexEntry[] {
|
|
|
125
149
|
.getCommands()
|
|
126
150
|
.filter((c) => c.source === "skill")
|
|
127
151
|
.map((c) => {
|
|
128
|
-
|
|
152
|
+
// pi 1.1.0: SlashCommandInfo 不再含 path 字段,用 sourceInfo.path 替代
|
|
153
|
+
const p = normalizeReadPath(c.sourceInfo.path, cwd);
|
|
129
154
|
return {
|
|
130
155
|
name: normalizeSkillName(c.name),
|
|
131
156
|
skillFilePath: p,
|
|
@@ -225,9 +250,9 @@ function renderUsageBar(
|
|
|
225
250
|
if (total <= 0) return "";
|
|
226
251
|
|
|
227
252
|
const toCols = (n: number) => Math.round((n / total) * w);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
253
|
+
const sys = toCols(parts.system);
|
|
254
|
+
const tools = toCols(parts.tools);
|
|
255
|
+
const con = toCols(parts.convo);
|
|
231
256
|
let rem = w - sys - tools - con;
|
|
232
257
|
if (rem < 0) rem = 0;
|
|
233
258
|
// adjust rounding drift
|
|
@@ -246,26 +271,28 @@ function joinComma(items: string[]): string {
|
|
|
246
271
|
return items.join(", ");
|
|
247
272
|
}
|
|
248
273
|
|
|
249
|
-
function joinCommaStyled(
|
|
274
|
+
function joinCommaStyled(
|
|
275
|
+
items: string[],
|
|
276
|
+
renderItem: (item: string) => string,
|
|
277
|
+
sep: string,
|
|
278
|
+
): string {
|
|
250
279
|
return items.map(renderItem).join(sep);
|
|
251
280
|
}
|
|
252
281
|
|
|
253
282
|
type ContextViewData = {
|
|
254
|
-
usage:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
| null;
|
|
283
|
+
usage: {
|
|
284
|
+
// message-based context usage estimate from ctx.getContextUsage()
|
|
285
|
+
messageTokens: number;
|
|
286
|
+
contextWindow: number;
|
|
287
|
+
// effective usage incl. a rough tool-definition estimate
|
|
288
|
+
effectiveTokens: number;
|
|
289
|
+
percent: number;
|
|
290
|
+
remainingTokens: number;
|
|
291
|
+
systemPromptTokens: number;
|
|
292
|
+
agentTokens: number;
|
|
293
|
+
toolsTokens: number;
|
|
294
|
+
activeTools: number;
|
|
295
|
+
} | null;
|
|
269
296
|
agentFiles: string[];
|
|
270
297
|
extensions: string[];
|
|
271
298
|
skills: string[];
|
|
@@ -274,16 +301,13 @@ type ContextViewData = {
|
|
|
274
301
|
};
|
|
275
302
|
|
|
276
303
|
class ContextView implements Component {
|
|
277
|
-
private tui: TUI;
|
|
278
304
|
private theme: any;
|
|
279
305
|
private onDone: () => void;
|
|
280
306
|
private data: ContextViewData;
|
|
281
307
|
private container: Container;
|
|
282
308
|
private body: Text;
|
|
283
309
|
private cachedWidth?: number;
|
|
284
|
-
|
|
285
|
-
constructor(tui: TUI, theme: any, data: ContextViewData, onDone: () => void) {
|
|
286
|
-
this.tui = tui;
|
|
310
|
+
constructor(_tui: TUI, theme: any, data: ContextViewData, onDone: () => void) {
|
|
287
311
|
this.theme = theme;
|
|
288
312
|
this.data = data;
|
|
289
313
|
this.onDone = onDone;
|
|
@@ -292,7 +316,8 @@ class ContextView implements Component {
|
|
|
292
316
|
this.container.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
293
317
|
this.container.addChild(
|
|
294
318
|
new Text(
|
|
295
|
-
theme.fg("accent", theme.bold("Context")) +
|
|
319
|
+
theme.fg("accent", theme.bold("Context")) +
|
|
320
|
+
theme.fg("dim", " (Esc/q/Enter to close)"),
|
|
296
321
|
1,
|
|
297
322
|
0,
|
|
298
323
|
),
|
|
@@ -320,8 +345,12 @@ class ContextView implements Component {
|
|
|
320
345
|
const u = this.data.usage;
|
|
321
346
|
lines.push(
|
|
322
347
|
muted("Window: ") +
|
|
323
|
-
text(
|
|
324
|
-
|
|
348
|
+
text(
|
|
349
|
+
`~${u.effectiveTokens.toLocaleString()} / ${u.contextWindow.toLocaleString()}`,
|
|
350
|
+
) +
|
|
351
|
+
muted(
|
|
352
|
+
` (${u.percent.toFixed(1)}% used, ~${u.remainingTokens.toLocaleString()} left)`,
|
|
353
|
+
),
|
|
325
354
|
);
|
|
326
355
|
|
|
327
356
|
// bar width tries to fit within the viewport
|
|
@@ -374,15 +403,28 @@ class ContextView implements Component {
|
|
|
374
403
|
);
|
|
375
404
|
}
|
|
376
405
|
|
|
377
|
-
lines.push(
|
|
406
|
+
lines.push(
|
|
407
|
+
muted(`AGENTS (${this.data.agentFiles.length}): `) +
|
|
408
|
+
text(
|
|
409
|
+
this.data.agentFiles.length ? joinComma(this.data.agentFiles) : "(none)",
|
|
410
|
+
),
|
|
411
|
+
);
|
|
378
412
|
lines.push("");
|
|
379
|
-
lines.push(
|
|
413
|
+
lines.push(
|
|
414
|
+
muted(`Extensions (${this.data.extensions.length}): `) +
|
|
415
|
+
text(
|
|
416
|
+
this.data.extensions.length ? joinComma(this.data.extensions) : "(none)",
|
|
417
|
+
),
|
|
418
|
+
);
|
|
380
419
|
|
|
381
420
|
const loaded = new Set(this.data.loadedSkills);
|
|
382
421
|
const skillsRendered = this.data.skills.length
|
|
383
422
|
? joinCommaStyled(
|
|
384
423
|
this.data.skills,
|
|
385
|
-
(name) =>
|
|
424
|
+
(name) =>
|
|
425
|
+
loaded.has(name)
|
|
426
|
+
? this.theme.fg("success", name)
|
|
427
|
+
: this.theme.fg("muted", name),
|
|
386
428
|
this.theme.fg("muted", ", "),
|
|
387
429
|
)
|
|
388
430
|
: "(none)";
|
|
@@ -444,7 +486,10 @@ export default function contextExtension(pi: ExtensionAPI) {
|
|
|
444
486
|
let best: SkillIndexEntry | null = null;
|
|
445
487
|
for (const s of cachedSkillIndex) {
|
|
446
488
|
if (!s.skillDir) continue;
|
|
447
|
-
if (
|
|
489
|
+
if (
|
|
490
|
+
absPath === s.skillFilePath ||
|
|
491
|
+
absPath.startsWith(s.skillDir + path.sep)
|
|
492
|
+
) {
|
|
448
493
|
if (!best || s.skillDir.length > best.skillDir.length) best = s;
|
|
449
494
|
}
|
|
450
495
|
}
|
|
@@ -467,7 +512,10 @@ export default function contextExtension(pi: ExtensionAPI) {
|
|
|
467
512
|
|
|
468
513
|
if (!cachedLoadedSkills.has(skillName)) {
|
|
469
514
|
cachedLoadedSkills.add(skillName);
|
|
470
|
-
pi.appendEntry<SkillLoadedEntryData>(SKILL_LOADED_ENTRY, {
|
|
515
|
+
pi.appendEntry<SkillLoadedEntryData>(SKILL_LOADED_ENTRY, {
|
|
516
|
+
name: skillName,
|
|
517
|
+
path: abs,
|
|
518
|
+
});
|
|
471
519
|
}
|
|
472
520
|
});
|
|
473
521
|
|
|
@@ -480,7 +528,8 @@ export default function contextExtension(pi: ExtensionAPI) {
|
|
|
480
528
|
|
|
481
529
|
const extensionsByPath = new Map<string, string[]>();
|
|
482
530
|
for (const c of extensionCmds) {
|
|
483
|
-
|
|
531
|
+
// pi 1.1.0: SlashCommandInfo 不再含 path 字段
|
|
532
|
+
const p = c.sourceInfo.path;
|
|
484
533
|
const arr = extensionsByPath.get(p) ?? [];
|
|
485
534
|
arr.push(c.name);
|
|
486
535
|
extensionsByPath.set(p, arr);
|
|
@@ -509,7 +558,9 @@ export default function contextExtension(pi: ExtensionAPI) {
|
|
|
509
558
|
// factor to account for parameters/schema/formatting.
|
|
510
559
|
const TOOL_FUDGE = 1.5;
|
|
511
560
|
const activeToolNames = pi.getActiveTools();
|
|
512
|
-
const toolInfoByName = new Map(
|
|
561
|
+
const toolInfoByName = new Map(
|
|
562
|
+
pi.getAllTools().map((t) => [t.name, t] as const),
|
|
563
|
+
);
|
|
513
564
|
let toolsTokens = 0;
|
|
514
565
|
for (const name of activeToolNames) {
|
|
515
566
|
const info = toolInfoByName.get(name);
|
|
@@ -520,7 +571,8 @@ export default function contextExtension(pi: ExtensionAPI) {
|
|
|
520
571
|
|
|
521
572
|
const effectiveTokens = messageTokens + toolsTokens;
|
|
522
573
|
const percent = ctxWindow > 0 ? (effectiveTokens / ctxWindow) * 100 : 0;
|
|
523
|
-
const remainingTokens =
|
|
574
|
+
const remainingTokens =
|
|
575
|
+
ctxWindow > 0 ? Math.max(0, ctxWindow - effectiveTokens) : 0;
|
|
524
576
|
|
|
525
577
|
const sessionUsage = sumSessionUsage(ctx);
|
|
526
578
|
|
|
@@ -534,41 +586,61 @@ export default function contextExtension(pi: ExtensionAPI) {
|
|
|
534
586
|
} else {
|
|
535
587
|
lines.push("Window: (unknown)");
|
|
536
588
|
}
|
|
537
|
-
lines.push(
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
lines.push(
|
|
541
|
-
|
|
542
|
-
|
|
589
|
+
lines.push(
|
|
590
|
+
`System: ~${systemPromptTokens.toLocaleString()} tok (AGENTS ~${agentTokens.toLocaleString()})`,
|
|
591
|
+
);
|
|
592
|
+
lines.push(
|
|
593
|
+
`Tools: ~${toolsTokens.toLocaleString()} tok (${activeToolNames.length} active)`,
|
|
594
|
+
);
|
|
595
|
+
lines.push(
|
|
596
|
+
`AGENTS: ${agentFilePaths.length ? joinComma(agentFilePaths) : "(none)"}`,
|
|
597
|
+
);
|
|
598
|
+
lines.push(
|
|
599
|
+
`Extensions (${extensionFiles.length}): ${extensionFiles.length ? joinComma(extensionFiles) : "(none)"}`,
|
|
600
|
+
);
|
|
601
|
+
lines.push(
|
|
602
|
+
`Skills (${skills.length}): ${skills.length ? joinComma(skills) : "(none)"}`,
|
|
603
|
+
);
|
|
604
|
+
lines.push(
|
|
605
|
+
`Session: ${sessionUsage.totalTokens.toLocaleString()} tokens · ${formatUsd(sessionUsage.totalCost)}`,
|
|
606
|
+
);
|
|
543
607
|
return lines.join("\n");
|
|
544
608
|
};
|
|
545
609
|
|
|
546
610
|
if (!ctx.hasUI) {
|
|
547
|
-
pi.sendMessage(
|
|
611
|
+
pi.sendMessage(
|
|
612
|
+
{ customType: "context", content: makePlainText(), display: true },
|
|
613
|
+
{ triggerTurn: false },
|
|
614
|
+
);
|
|
548
615
|
return;
|
|
549
616
|
}
|
|
550
617
|
|
|
551
|
-
const loadedSkills = Array.from(getLoadedSkillsFromSession(ctx)).sort(
|
|
618
|
+
const loadedSkills = Array.from(getLoadedSkillsFromSession(ctx)).sort(
|
|
619
|
+
(a, b) => a.localeCompare(b),
|
|
620
|
+
);
|
|
552
621
|
|
|
553
622
|
const viewData: ContextViewData = {
|
|
554
623
|
usage: usage
|
|
555
624
|
? {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
625
|
+
messageTokens,
|
|
626
|
+
contextWindow: ctxWindow,
|
|
627
|
+
effectiveTokens,
|
|
628
|
+
percent,
|
|
629
|
+
remainingTokens,
|
|
630
|
+
systemPromptTokens,
|
|
631
|
+
agentTokens,
|
|
632
|
+
toolsTokens,
|
|
633
|
+
activeTools: activeToolNames.length,
|
|
634
|
+
}
|
|
566
635
|
: null,
|
|
567
636
|
agentFiles: agentFilePaths,
|
|
568
637
|
extensions: extensionFiles,
|
|
569
638
|
skills,
|
|
570
639
|
loadedSkills,
|
|
571
|
-
session: {
|
|
640
|
+
session: {
|
|
641
|
+
totalTokens: sessionUsage.totalTokens,
|
|
642
|
+
totalCost: sessionUsage.totalCost,
|
|
643
|
+
},
|
|
572
644
|
};
|
|
573
645
|
|
|
574
646
|
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
@@ -108,11 +108,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
108
108
|
|
|
109
109
|
// ── Assemble: figure out what fits ──────────────────────
|
|
110
110
|
// Always show: path | ctx | model. Stats only if room.
|
|
111
|
-
const fixedRight = ctxRaw + modelRaw;
|
|
112
|
-
const fixedRightWidth = visibleWidth(fixedRight);
|
|
113
|
-
const sepsNeeded = statsRaw ? 3 : 2; // number of separators
|
|
114
|
-
const sepsWidth = sepsNeeded * sepWidth;
|
|
115
|
-
|
|
116
111
|
// Available width for path after reserving right segments
|
|
117
112
|
const rightBlockWidth = (statsRaw ? visibleWidth(statsRaw) + sepWidth : 0)
|
|
118
113
|
+ visibleWidth(ctxRaw) + sepWidth + visibleWidth(modelRaw);
|
|
@@ -2,126 +2,108 @@
|
|
|
2
2
|
* @author: xujianjiang
|
|
3
3
|
* 自定义扩展示例
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 放置位置:config/agent/extensions/
|
|
6
6
|
* 安装后会复制到 ~/.pi/agent/extensions/
|
|
7
7
|
*
|
|
8
8
|
* 可以拦截内置工具、注册自定义工具和命令
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
13
|
-
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
14
12
|
|
|
15
13
|
export default function (pi: ExtensionAPI) {
|
|
16
|
-
// ===
|
|
17
|
-
let agentStartTime = 0;
|
|
18
|
-
|
|
19
|
-
pi.on("agent_start", async () => {
|
|
20
|
-
agentStartTime = Date.now();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// === 消息完成通知(只在最后一条显示)===
|
|
14
|
+
// === 拦截工具调用 ===
|
|
24
15
|
|
|
25
|
-
pi.on("
|
|
16
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
26
17
|
try {
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
timeStr = `${elapsed}ms`;
|
|
32
|
-
} else if (elapsed < 60000) {
|
|
33
|
-
timeStr = `${(elapsed / 1000).toFixed(1)}s`;
|
|
34
|
-
} else {
|
|
35
|
-
const mins = Math.floor(elapsed / 60000);
|
|
36
|
-
const secs = Math.floor((elapsed % 60000) / 1000);
|
|
37
|
-
timeStr = `${mins}m ${secs}s`;
|
|
18
|
+
// 拦截 login 工具 - 阻止执行
|
|
19
|
+
if (event.toolName === "login") {
|
|
20
|
+
await ctx.ui.notify("⚠️ login 功能已禁用", "warning");
|
|
21
|
+
return { block: true, reason: "login 功能已禁用" };
|
|
38
22
|
}
|
|
39
23
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
ctx.ui.notify(`${timeStr2} | ✨ 消息处理完成 (${timeStr})`, "info");
|
|
24
|
+
// 拦截 logout 工具 - 阻止执行
|
|
25
|
+
if (event.toolName === "logout") {
|
|
26
|
+
await ctx.ui.notify("⚠️ logout 功能已禁用", "warning");
|
|
27
|
+
return { block: true, reason: "logout 功能已禁用" };
|
|
28
|
+
}
|
|
46
29
|
} catch (e) {
|
|
47
30
|
// 忽略错误(ctx 可能已过期)
|
|
48
31
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// === 拦截工具调用 ===
|
|
52
|
-
|
|
53
|
-
pi.on("tool_call", async (event, ctx) => {
|
|
54
|
-
// 拦截 login 工具 - 阻止执行
|
|
55
|
-
if (event.toolName === "login") {
|
|
56
|
-
await ctx.ui.notify("⚠️ login 功能已禁用", "warning");
|
|
57
|
-
return { block: true, reason: "login 功能已禁用" };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 拦截 logout 工具 - 阻止执行
|
|
61
|
-
if (event.toolName === "logout") {
|
|
62
|
-
await ctx.ui.notify("⚠️ logout 功能已禁用", "warning");
|
|
63
|
-
return { block: true, reason: "logout 功能已禁用" };
|
|
64
|
-
}
|
|
32
|
+
return;
|
|
65
33
|
});
|
|
66
34
|
|
|
67
35
|
// === 注册自定义命令 ===
|
|
68
36
|
|
|
69
37
|
pi.registerCommand("hello", {
|
|
70
38
|
description: "打招呼命令",
|
|
71
|
-
usage: "/hello [名字]",
|
|
72
39
|
handler: async (args, ctx) => {
|
|
73
|
-
|
|
74
|
-
|
|
40
|
+
try {
|
|
41
|
+
const name = args || "大佬,您吃了吗";
|
|
42
|
+
ctx.ui.notify(`你好,${name}!🤝`, "info");
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// 忽略错误
|
|
45
|
+
}
|
|
75
46
|
},
|
|
76
47
|
});
|
|
77
48
|
|
|
78
49
|
pi.registerCommand("status", {
|
|
79
50
|
description: "显示当前会话状态",
|
|
80
51
|
handler: async (_args, ctx) => {
|
|
81
|
-
|
|
52
|
+
try {
|
|
53
|
+
// 保存需要的数据,避免多次触发 getter
|
|
54
|
+
const model = ctx.model?.name || "未知";
|
|
55
|
+
const entries = ctx.sessionManager.getEntries();
|
|
56
|
+
const messages = entries.filter((e) => e.type === "message").length;
|
|
57
|
+
|
|
58
|
+
const info = `
|
|
82
59
|
📊 会话状态
|
|
83
60
|
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
- 消息数量: ${ctx.session.messages?.length || 0}
|
|
61
|
+
- 当前模型:${model}
|
|
62
|
+
- 消息数量:${messages}
|
|
87
63
|
`.trim();
|
|
88
|
-
|
|
64
|
+
ctx.ui.notify(info, "info");
|
|
65
|
+
} catch (e) {
|
|
66
|
+
// 忽略错误(ctx 可能已过期)
|
|
67
|
+
}
|
|
89
68
|
},
|
|
90
69
|
});
|
|
91
70
|
|
|
92
71
|
pi.registerCommand("clear", {
|
|
93
72
|
description: "清理当前会话上下文",
|
|
94
|
-
usage: "/clear",
|
|
95
73
|
handler: async (_args, ctx) => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const ok = await ctx.ui.confirm("清理会话", `确定要清理当前会话的所有 ${msgCount} 条消息吗?`);
|
|
107
|
-
if (!ok) {
|
|
108
|
-
ctx.ui.notify("已取消", "info");
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
74
|
+
try {
|
|
75
|
+
// 获取当前会话条目数(在 await 前完成所有 ctx 访问)
|
|
76
|
+
const entries = ctx.sessionManager.getBranch();
|
|
77
|
+
const msgCount = entries.filter((e) => e.type === "message").length;
|
|
78
|
+
const uiContext = ctx.ui; // ← 保存 UI context
|
|
79
|
+
|
|
80
|
+
if (msgCount === 0) {
|
|
81
|
+
uiContext.notify("会话已经是空的", "info");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
111
84
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
85
|
+
// 确认清理
|
|
86
|
+
const ok = await uiContext.confirm(
|
|
87
|
+
"清理会话",
|
|
88
|
+
`确定要清理当前会话的所有 ${msgCount} 条消息吗?`,
|
|
89
|
+
);
|
|
90
|
+
if (!ok) {
|
|
91
|
+
uiContext.notify("已取消", "info");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
118
94
|
|
|
119
|
-
|
|
120
|
-
ctx.
|
|
121
|
-
|
|
95
|
+
// 创建新会话来清空上下文
|
|
96
|
+
await ctx.newSession({
|
|
97
|
+
withSession: async (newCtx) => {
|
|
98
|
+
newCtx.ui.notify(
|
|
99
|
+
`✅ 已清理 ${msgCount} 条消息,进入新会话`,
|
|
100
|
+
"info",
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// 忽略错误(ctx 可能已过期)
|
|
122
106
|
}
|
|
123
|
-
|
|
124
|
-
ctx.ui.notify(`✅ 已清理 ${msgCount} 条消息,进入新会话`, "success");
|
|
125
107
|
},
|
|
126
108
|
});
|
|
127
109
|
|
|
@@ -129,10 +111,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
129
111
|
|
|
130
112
|
pi.registerCommand("exit", {
|
|
131
113
|
description: "退出当前会话",
|
|
132
|
-
usage: "/exit",
|
|
133
114
|
handler: async (_args, ctx) => {
|
|
134
|
-
|
|
135
|
-
|
|
115
|
+
try {
|
|
116
|
+
ctx.ui.notify("👋 再见!", "info");
|
|
117
|
+
ctx.shutdown();
|
|
118
|
+
} catch (e) {
|
|
119
|
+
// 忽略错误
|
|
120
|
+
}
|
|
136
121
|
},
|
|
137
122
|
});
|
|
138
123
|
|
|
@@ -140,36 +125,52 @@ export default function (pi: ExtensionAPI) {
|
|
|
140
125
|
|
|
141
126
|
pi.registerCommand("update", {
|
|
142
127
|
description: "更新 aicode-cli 工具",
|
|
143
|
-
usage: "/update",
|
|
144
128
|
handler: async (_args, ctx) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
ctx.ui
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
} else {
|
|
157
|
-
ctx.ui.notify("✅ aicode-cli 更新成功!请重启 CLI", "success");
|
|
129
|
+
try {
|
|
130
|
+
// ← 在 await 前保存 UI context
|
|
131
|
+
const uiContext = ctx.ui;
|
|
132
|
+
|
|
133
|
+
const ok = await uiContext.confirm(
|
|
134
|
+
"更新确认",
|
|
135
|
+
"确定要更新 aicode-cli 吗?",
|
|
136
|
+
);
|
|
137
|
+
if (!ok) {
|
|
138
|
+
uiContext.notify("已取消", "info");
|
|
139
|
+
return;
|
|
158
140
|
}
|
|
159
|
-
|
|
141
|
+
|
|
142
|
+
uiContext.notify("🔄 正在更新 aicode-cli...", "info");
|
|
143
|
+
|
|
144
|
+
// 使用 Promise 包装 exec
|
|
145
|
+
const { exec } = await import("child_process");
|
|
146
|
+
await new Promise<void>((resolve) => {
|
|
147
|
+
exec("npm install -g @ia-ccun/code-agent-cli", (error) => {
|
|
148
|
+
if (error) {
|
|
149
|
+
uiContext.notify(
|
|
150
|
+
`❌ 更新失败:${error.message},请重试`,
|
|
151
|
+
"error",
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
uiContext.notify("✅ aicode-cli 更新成功!请重启 CLI", "info");
|
|
155
|
+
}
|
|
156
|
+
resolve();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
} catch (e) {
|
|
160
|
+
// 忽略错误(ctx 可能已过期)
|
|
161
|
+
}
|
|
160
162
|
},
|
|
161
163
|
});
|
|
162
164
|
|
|
163
165
|
// === 注册自定义工具 ===
|
|
164
166
|
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
+
// 注意:注册自定义工具需要更复杂的参数定义
|
|
168
|
+
// 详情请参考:https://github.com/mariozechner/pi-coding-agent/docs/extensions.md
|
|
167
169
|
|
|
168
170
|
// === 注册 /version 命令 ===
|
|
169
171
|
|
|
170
172
|
pi.registerCommand("version", {
|
|
171
173
|
description: "查看当前版本和最新版本",
|
|
172
|
-
usage: "/version",
|
|
173
174
|
handler: async (_args, ctx) => {
|
|
174
175
|
const { execSync } = await import("child_process");
|
|
175
176
|
const { readFileSync, existsSync } = await import("fs");
|
|
@@ -193,18 +194,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
193
194
|
let latestVersion = "未知";
|
|
194
195
|
let updateMsg = "";
|
|
195
196
|
try {
|
|
196
|
-
const registry = execSync("npm config get registry", {
|
|
197
|
-
|
|
197
|
+
const registry = execSync("npm config get registry", {
|
|
198
|
+
encoding: "utf8",
|
|
199
|
+
}).trim();
|
|
200
|
+
latestVersion = execSync(
|
|
201
|
+
`npm view @ia-ccun/code-agent-cli version --registry=${registry}`,
|
|
202
|
+
{ encoding: "utf8" },
|
|
203
|
+
).trim();
|
|
198
204
|
|
|
199
205
|
// 比较版本
|
|
200
206
|
const localParts = localVersion.split(".").map(Number);
|
|
201
207
|
const latestParts = latestVersion.split(".").map(Number);
|
|
202
|
-
const needsUpdate =
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
const needsUpdate =
|
|
209
|
+
latestParts[0] > localParts[0] ||
|
|
210
|
+
(latestParts[0] === localParts[0] &&
|
|
211
|
+
latestParts[1] > localParts[1]) ||
|
|
212
|
+
(latestParts[0] === localParts[0] &&
|
|
213
|
+
latestParts[1] === localParts[1] &&
|
|
214
|
+
latestParts[2] > localParts[2]);
|
|
205
215
|
|
|
206
216
|
if (needsUpdate) {
|
|
207
|
-
updateMsg = `\n\n⚠️ 发现新版本 v${localVersion} → v${latestVersion}\n
|
|
217
|
+
updateMsg = `\n\n⚠️ 发现新版本 v${localVersion} → v${latestVersion}\n 运行:npm install -g @ia-ccun/code-agent-cli`;
|
|
208
218
|
} else {
|
|
209
219
|
updateMsg = "\n\n✅ 已是最新版本";
|
|
210
220
|
}
|
|
@@ -215,8 +225,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
215
225
|
const info = `
|
|
216
226
|
📦 版本信息
|
|
217
227
|
|
|
218
|
-
|
|
219
|
-
|
|
228
|
+
当前版本:v${localVersion}
|
|
229
|
+
最新版本:v${latestVersion}
|
|
220
230
|
${updateMsg}
|
|
221
231
|
`.trim();
|
|
222
232
|
|