@ia-ccun/code-agent-cli 0.0.53-beta.1 → 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 +92 -68
- package/config/agent/extensions/aicode-advisor.ts +187 -64
- package/config/agent/extensions/aicode-banner.ts +101 -0
- package/config/agent/extensions/aicode-btw.ts +622 -180
- package/config/agent/extensions/aicode-cd.ts +246 -63
- package/config/agent/extensions/aicode-pets.ts +0 -11
- package/config/agent/extensions/aicode-plan.ts +2 -0
- package/config/agent/extensions/aicode-safety-guard.ts +3 -0
- package/config/agent/extensions/aicode-tips.ts +1 -16
- 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 +41 -29
- package/config/agent/extensions/error-monitor.ts +37 -33
- package/config/agent/extensions/goal.ts +8 -6
- package/config/agent/extensions/msg-debug.ts +103 -112
- package/config/agent/extensions/review.ts +365 -164
- package/config/agent/extensions/working-msg.ts +2 -4
- 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 +6 -7
- package/scripts/regression/results/.last_pid +1 -1
- package/scripts/regression/results/.last_workdir +1 -1
- package/scripts/regression/results/02-extension-loading.log +16 -27
- package/scripts/regression/results/03-prompt-stream.log +14 -23
- package/scripts/regression/results/03-rpc-protocol.log +20 -31
- package/scripts/regression/results/04-slash-commands-and-skills.log +11 -10
- package/scripts/regression/results/05-tool-execution.log +6 -4
- package/scripts/regression/results/06-pi-new-features.log +22 -26
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
* - 100% : 阻止继续并强制提示压缩
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type {
|
|
18
|
-
|
|
17
|
+
import type {
|
|
18
|
+
ExtensionAPI,
|
|
19
|
+
ExtensionContext,
|
|
20
|
+
} from "@earendil-works/pi-coding-agent";
|
|
19
21
|
interface ContextThresholds {
|
|
20
|
-
info: number;
|
|
22
|
+
info: number; // 70%
|
|
21
23
|
warning: number; // 80%
|
|
22
|
-
error: number;
|
|
24
|
+
error: number; // 90%
|
|
23
25
|
critical: number; // 100%
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -50,7 +52,9 @@ function formatTokens(n: number): string {
|
|
|
50
52
|
return `${(n / 1000000).toFixed(1)}M`;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
function getContextLevel(
|
|
55
|
+
function getContextLevel(
|
|
56
|
+
percent: number,
|
|
57
|
+
): "normal" | "info" | "warning" | "error" | "critical" {
|
|
54
58
|
if (percent >= DEFAULT_THRESHOLDS.critical) return "critical";
|
|
55
59
|
if (percent >= DEFAULT_THRESHOLDS.error) return "error";
|
|
56
60
|
if (percent >= DEFAULT_THRESHOLDS.warning) return "warning";
|
|
@@ -58,7 +62,11 @@ function getContextLevel(percent: number): "normal" | "info" | "warning" | "erro
|
|
|
58
62
|
return "normal";
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
function getContextMessage(stats: ContextStats): {
|
|
65
|
+
function getContextMessage(stats: ContextStats): {
|
|
66
|
+
level: "info" | "warning" | "error" | "normal" | "critical";
|
|
67
|
+
title: string;
|
|
68
|
+
body: string;
|
|
69
|
+
} {
|
|
62
70
|
const level = getContextLevel(stats.percent);
|
|
63
71
|
const contextBar = renderContextBar(stats.percent);
|
|
64
72
|
|
|
@@ -97,7 +105,11 @@ function renderContextBar(percent: number, width: number = 20): string {
|
|
|
97
105
|
const filledChar = "█";
|
|
98
106
|
const emptyChar = "░";
|
|
99
107
|
|
|
100
|
-
return
|
|
108
|
+
return (
|
|
109
|
+
filledChar.repeat(filled) +
|
|
110
|
+
emptyChar.repeat(empty) +
|
|
111
|
+
` ${percent.toFixed(1)}%`
|
|
112
|
+
);
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
export default function contextMonitorExtension(pi: ExtensionAPI) {
|
|
@@ -127,9 +139,12 @@ export default function contextMonitorExtension(pi: ExtensionAPI) {
|
|
|
127
139
|
pi.getActiveTools().reduce((sum, name) => {
|
|
128
140
|
const info = pi.getAllTools().find((t) => t.name === name);
|
|
129
141
|
return sum + estimateTokens(`${name}\n${info?.description || ""}`);
|
|
130
|
-
}, 0) * 1.5
|
|
142
|
+
}, 0) * 1.5,
|
|
143
|
+
);
|
|
144
|
+
const conversationTokens = Math.max(
|
|
145
|
+
0,
|
|
146
|
+
usedTokens - systemTokens - toolsTokens,
|
|
131
147
|
);
|
|
132
|
-
const conversationTokens = Math.max(0, usedTokens - systemTokens - toolsTokens);
|
|
133
148
|
|
|
134
149
|
return {
|
|
135
150
|
contextWindow,
|
|
@@ -145,7 +160,10 @@ export default function contextMonitorExtension(pi: ExtensionAPI) {
|
|
|
145
160
|
/**
|
|
146
161
|
* 处理上下文警告
|
|
147
162
|
*/
|
|
148
|
-
async function handleContextWarning(
|
|
163
|
+
async function handleContextWarning(
|
|
164
|
+
stats: ContextStats,
|
|
165
|
+
ctx: ExtensionContext,
|
|
166
|
+
) {
|
|
149
167
|
if (!autoWarnEnabled) return;
|
|
150
168
|
|
|
151
169
|
const level = getContextLevel(stats.percent);
|
|
@@ -180,7 +198,7 @@ export default function contextMonitorExtension(pi: ExtensionAPI) {
|
|
|
180
198
|
});
|
|
181
199
|
|
|
182
200
|
// 监听会话开始,初始化
|
|
183
|
-
pi.on("session_start", async (_event,
|
|
201
|
+
pi.on("session_start", async (_event, _ctx: ExtensionContext) => {
|
|
184
202
|
lastWarnedLevel = "normal";
|
|
185
203
|
warnCooldown = 0;
|
|
186
204
|
});
|
|
@@ -190,7 +208,6 @@ export default function contextMonitorExtension(pi: ExtensionAPI) {
|
|
|
190
208
|
*/
|
|
191
209
|
pi.registerCommand("context-stats", {
|
|
192
210
|
description: "显示当前上下文详细统计",
|
|
193
|
-
usage: "/context-stats",
|
|
194
211
|
handler: async (_args, ctx: ExtensionContext) => {
|
|
195
212
|
const stats = getContextStats(ctx);
|
|
196
213
|
|
|
@@ -200,7 +217,6 @@ export default function contextMonitorExtension(pi: ExtensionAPI) {
|
|
|
200
217
|
}
|
|
201
218
|
|
|
202
219
|
const message = getContextMessage(stats);
|
|
203
|
-
const lines = message.body.split("\n");
|
|
204
220
|
|
|
205
221
|
// 构建详细统计文本
|
|
206
222
|
const modelName = ctx.model?.name || "unknown";
|
|
@@ -244,7 +260,7 @@ export default function contextMonitorExtension(pi: ExtensionAPI) {
|
|
|
244
260
|
content: fullText,
|
|
245
261
|
display: true,
|
|
246
262
|
},
|
|
247
|
-
{ triggerTurn: false }
|
|
263
|
+
{ triggerTurn: false },
|
|
248
264
|
);
|
|
249
265
|
|
|
250
266
|
// 如果超过阈值也显示通知
|
|
@@ -259,7 +275,6 @@ export default function contextMonitorExtension(pi: ExtensionAPI) {
|
|
|
259
275
|
*/
|
|
260
276
|
pi.registerCommand("context-warn", {
|
|
261
277
|
description: "开启/关闭上下文警告",
|
|
262
|
-
usage: "/context-warn [on|off]",
|
|
263
278
|
handler: async (args, ctx: ExtensionContext) => {
|
|
264
279
|
if (!args || args === "on") {
|
|
265
280
|
autoWarnEnabled = true;
|
|
@@ -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);
|
|
@@ -9,12 +9,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
12
|
|
|
14
13
|
export default function (pi: ExtensionAPI) {
|
|
15
|
-
// === 记录开始时间 ===
|
|
16
|
-
let agentStartTime = 0;
|
|
17
|
-
|
|
18
14
|
// === 拦截工具调用 ===
|
|
19
15
|
|
|
20
16
|
pi.on("tool_call", async (event, ctx) => {
|
|
@@ -33,13 +29,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
33
29
|
} catch (e) {
|
|
34
30
|
// 忽略错误(ctx 可能已过期)
|
|
35
31
|
}
|
|
32
|
+
return;
|
|
36
33
|
});
|
|
37
34
|
|
|
38
35
|
// === 注册自定义命令 ===
|
|
39
36
|
|
|
40
37
|
pi.registerCommand("hello", {
|
|
41
38
|
description: "打招呼命令",
|
|
42
|
-
usage: "/hello [名字]",
|
|
43
39
|
handler: async (args, ctx) => {
|
|
44
40
|
try {
|
|
45
41
|
const name = args || "大佬,您吃了吗";
|
|
@@ -55,15 +51,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
55
51
|
handler: async (_args, ctx) => {
|
|
56
52
|
try {
|
|
57
53
|
// 保存需要的数据,避免多次触发 getter
|
|
58
|
-
const model = ctx.
|
|
59
|
-
const
|
|
60
|
-
const messages =
|
|
61
|
-
|
|
54
|
+
const model = ctx.model?.name || "未知";
|
|
55
|
+
const entries = ctx.sessionManager.getEntries();
|
|
56
|
+
const messages = entries.filter((e) => e.type === "message").length;
|
|
57
|
+
|
|
62
58
|
const info = `
|
|
63
59
|
📊 会话状态
|
|
64
60
|
|
|
65
61
|
- 当前模型:${model}
|
|
66
|
-
- 项目目录:${workspace}
|
|
67
62
|
- 消息数量:${messages}
|
|
68
63
|
`.trim();
|
|
69
64
|
ctx.ui.notify(info, "info");
|
|
@@ -75,13 +70,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
75
70
|
|
|
76
71
|
pi.registerCommand("clear", {
|
|
77
72
|
description: "清理当前会话上下文",
|
|
78
|
-
usage: "/clear",
|
|
79
73
|
handler: async (_args, ctx) => {
|
|
80
74
|
try {
|
|
81
75
|
// 获取当前会话条目数(在 await 前完成所有 ctx 访问)
|
|
82
76
|
const entries = ctx.sessionManager.getBranch();
|
|
83
|
-
const msgCount = entries.filter(e => e.type === "message").length;
|
|
84
|
-
const uiContext = ctx.ui;
|
|
77
|
+
const msgCount = entries.filter((e) => e.type === "message").length;
|
|
78
|
+
const uiContext = ctx.ui; // ← 保存 UI context
|
|
85
79
|
|
|
86
80
|
if (msgCount === 0) {
|
|
87
81
|
uiContext.notify("会话已经是空的", "info");
|
|
@@ -89,7 +83,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
89
83
|
}
|
|
90
84
|
|
|
91
85
|
// 确认清理
|
|
92
|
-
const ok = await uiContext.confirm(
|
|
86
|
+
const ok = await uiContext.confirm(
|
|
87
|
+
"清理会话",
|
|
88
|
+
`确定要清理当前会话的所有 ${msgCount} 条消息吗?`,
|
|
89
|
+
);
|
|
93
90
|
if (!ok) {
|
|
94
91
|
uiContext.notify("已取消", "info");
|
|
95
92
|
return;
|
|
@@ -98,7 +95,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
98
95
|
// 创建新会话来清空上下文
|
|
99
96
|
await ctx.newSession({
|
|
100
97
|
withSession: async (newCtx) => {
|
|
101
|
-
newCtx.ui.notify(
|
|
98
|
+
newCtx.ui.notify(
|
|
99
|
+
`✅ 已清理 ${msgCount} 条消息,进入新会话`,
|
|
100
|
+
"info",
|
|
101
|
+
);
|
|
102
102
|
},
|
|
103
103
|
});
|
|
104
104
|
} catch (e) {
|
|
@@ -111,7 +111,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
111
111
|
|
|
112
112
|
pi.registerCommand("exit", {
|
|
113
113
|
description: "退出当前会话",
|
|
114
|
-
usage: "/exit",
|
|
115
114
|
handler: async (_args, ctx) => {
|
|
116
115
|
try {
|
|
117
116
|
ctx.ui.notify("👋 再见!", "info");
|
|
@@ -126,28 +125,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
126
125
|
|
|
127
126
|
pi.registerCommand("update", {
|
|
128
127
|
description: "更新 aicode-cli 工具",
|
|
129
|
-
usage: "/update",
|
|
130
128
|
handler: async (_args, ctx) => {
|
|
131
129
|
try {
|
|
132
130
|
// ← 在 await 前保存 UI context
|
|
133
131
|
const uiContext = ctx.ui;
|
|
134
|
-
|
|
135
|
-
const ok = await uiContext.confirm(
|
|
132
|
+
|
|
133
|
+
const ok = await uiContext.confirm(
|
|
134
|
+
"更新确认",
|
|
135
|
+
"确定要更新 aicode-cli 吗?",
|
|
136
|
+
);
|
|
136
137
|
if (!ok) {
|
|
137
138
|
uiContext.notify("已取消", "info");
|
|
138
139
|
return;
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
uiContext.notify("🔄 正在更新 aicode-cli...", "info");
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
// 使用 Promise 包装 exec
|
|
144
145
|
const { exec } = await import("child_process");
|
|
145
146
|
await new Promise<void>((resolve) => {
|
|
146
|
-
exec("npm install -g @ia-ccun/code-agent-cli", (error
|
|
147
|
+
exec("npm install -g @ia-ccun/code-agent-cli", (error) => {
|
|
147
148
|
if (error) {
|
|
148
|
-
uiContext.notify(
|
|
149
|
+
uiContext.notify(
|
|
150
|
+
`❌ 更新失败:${error.message},请重试`,
|
|
151
|
+
"error",
|
|
152
|
+
);
|
|
149
153
|
} else {
|
|
150
|
-
uiContext.notify("✅ aicode-cli 更新成功!请重启 CLI", "
|
|
154
|
+
uiContext.notify("✅ aicode-cli 更新成功!请重启 CLI", "info");
|
|
151
155
|
}
|
|
152
156
|
resolve();
|
|
153
157
|
});
|
|
@@ -167,7 +171,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
167
171
|
|
|
168
172
|
pi.registerCommand("version", {
|
|
169
173
|
description: "查看当前版本和最新版本",
|
|
170
|
-
usage: "/version",
|
|
171
174
|
handler: async (_args, ctx) => {
|
|
172
175
|
const { execSync } = await import("child_process");
|
|
173
176
|
const { readFileSync, existsSync } = await import("fs");
|
|
@@ -191,15 +194,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
191
194
|
let latestVersion = "未知";
|
|
192
195
|
let updateMsg = "";
|
|
193
196
|
try {
|
|
194
|
-
const registry = execSync("npm config get registry", {
|
|
195
|
-
|
|
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();
|
|
196
204
|
|
|
197
205
|
// 比较版本
|
|
198
206
|
const localParts = localVersion.split(".").map(Number);
|
|
199
207
|
const latestParts = latestVersion.split(".").map(Number);
|
|
200
|
-
const needsUpdate =
|
|
201
|
-
|
|
202
|
-
|
|
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]);
|
|
203
215
|
|
|
204
216
|
if (needsUpdate) {
|
|
205
217
|
updateMsg = `\n\n⚠️ 发现新版本 v${localVersion} → v${latestVersion}\n 运行:npm install -g @ia-ccun/code-agent-cli`;
|