@ia-ccun/code-agent-cli 0.0.54-beta.1 → 0.0.54-beta.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/bin/cli.js +116 -74
- 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 +619 -182
- 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 +42 -24
- 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/config/agent/settings.json +15 -14
- package/config/agent/skills/hello/SKILL.md +1 -1
- package/config/agent/skills/refactor/SKILL.md +40 -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 +6 -7
- 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/cases/08-cwd-resilience.sh +84 -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 +19 -0
- package/scripts/regression/results/03-prompt-stream.log +16 -0
- package/scripts/regression/results/03-rpc-protocol.log +28 -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 +27 -0
- package/scripts/regression/results/08-cwd-resilience.log +1 -0
- package/scripts/regression/run-all.sh +187 -0
- package/config/agent/bin/fd.exe +0 -0
- package/config/agent/bin/rg.exe +0 -0
- package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
- package/config/agent/extensions/aicode-snake.ts +0 -354
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
- /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
- /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
- /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/references/stdio-protocol.md +0 -0
|
@@ -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) => {
|
|
@@ -6,10 +6,30 @@
|
|
|
6
6
|
* [gold] [dim] [dim] [color ctx] [accent]
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { basename } from "node:path";
|
|
9
10
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
10
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
12
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* 解析 footer 显示用的项目名.
|
|
16
|
+
* 优先用 process.env.PWD (shell 启动时设进 env, cli.js 入口 chdir(homedir()) 也不改它),
|
|
17
|
+
* 退化到 process.cwd(). /user/xxx 启动显示 xxx, 跟 git 无关.
|
|
18
|
+
* cwd 被删时 fallback 到 'project'.
|
|
19
|
+
*/
|
|
20
|
+
function resolveProjectName(): string {
|
|
21
|
+
const pwd = process.env.PWD;
|
|
22
|
+
if (pwd) {
|
|
23
|
+
const name = basename(pwd);
|
|
24
|
+
if (name) return name;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return basename(process.cwd()) || "project";
|
|
28
|
+
} catch {
|
|
29
|
+
return "project";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
13
33
|
function fmtTokens(n: number): string {
|
|
14
34
|
if (n < 1000) return n.toString();
|
|
15
35
|
if (n < 10000) return `${(n / 1000).toFixed(1)}k`;
|
|
@@ -22,7 +42,7 @@ const gold = (s: string) => `\x1b[38;2;73;188;207m${s}\x1b[0m`;
|
|
|
22
42
|
|
|
23
43
|
// 获取本地版本号
|
|
24
44
|
function getVersion() {
|
|
25
|
-
|
|
45
|
+
return process.env.AICODE_CLI_VERSION || null;
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
export default function (pi: ExtensionAPI) {
|
|
@@ -43,7 +63,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
43
63
|
try {
|
|
44
64
|
const { existsSync, readFileSync } = await import("fs");
|
|
45
65
|
if (existsSync(cachePath)) {
|
|
46
|
-
const cache = JSON.parse(readFileSync(cachePath,
|
|
66
|
+
const cache = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
47
67
|
// 缓存有效期 1 小时
|
|
48
68
|
if (cache.needsUpdate && Date.now() - cache.timestamp < 3600000) {
|
|
49
69
|
// 在输入框上方显示版本更新提示
|
|
@@ -68,8 +88,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
68
88
|
const sepWidth = 3; // " │ "
|
|
69
89
|
|
|
70
90
|
// ── Segment 1: project name (branch) ─────────────────────
|
|
71
|
-
|
|
72
|
-
const projectName =
|
|
91
|
+
// /user/xxx 启动显示 xxx (走 PWD env, 跟 chdir 解耦)
|
|
92
|
+
const projectName = resolveProjectName();
|
|
73
93
|
const branch = footerData.getGitBranch();
|
|
74
94
|
const pathRaw = projectName + (branch ? ` (${branch})` : "");
|
|
75
95
|
|
|
@@ -108,14 +128,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
108
128
|
|
|
109
129
|
// ── Assemble: figure out what fits ──────────────────────
|
|
110
130
|
// 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
131
|
// Available width for path after reserving right segments
|
|
117
|
-
const rightBlockWidth =
|
|
118
|
-
|
|
132
|
+
const rightBlockWidth =
|
|
133
|
+
(statsRaw ? visibleWidth(statsRaw) + sepWidth : 0) +
|
|
134
|
+
visibleWidth(ctxRaw) +
|
|
135
|
+
sepWidth +
|
|
136
|
+
visibleWidth(modelRaw);
|
|
119
137
|
const pathBudget = width - rightBlockWidth - sepWidth; // -sepWidth for first separator
|
|
120
138
|
|
|
121
139
|
let pathDisplay: string;
|
|
@@ -141,12 +159,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
141
159
|
|
|
142
160
|
// Thinking level coloring — semaphore, hardcoded to decouple from border
|
|
143
161
|
const thinkingColors: Record<string, string> = {
|
|
144
|
-
off:
|
|
145
|
-
minimal: "\x1b[38;2;67;61;78m",
|
|
146
|
-
low:
|
|
147
|
-
medium:
|
|
148
|
-
high:
|
|
149
|
-
xhigh:
|
|
162
|
+
off: "\x1b[38;2;51;45;62m", // surface1 — invisible
|
|
163
|
+
minimal: "\x1b[38;2;67;61;78m", // surface2 — barely there
|
|
164
|
+
low: "\x1b[38;2;166;227;161m", // green
|
|
165
|
+
medium: "\x1b[38;2;137;180;250m", // blue
|
|
166
|
+
high: "\x1b[38;2;250;179;135m", // peach
|
|
167
|
+
xhigh: "\x1b[38;2;243;139;168m", // red
|
|
150
168
|
};
|
|
151
169
|
const thinkAnsi = thinkingColors[thinking] ?? thinkingColors.off;
|
|
152
170
|
let modelColored = theme.fg("accent", `⚡ ${modelName}`);
|
|
@@ -160,11 +178,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
160
178
|
|
|
161
179
|
// quots
|
|
162
180
|
const quotes = [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
181
|
+
"敬畏生产,双人复核,稳妥变更",
|
|
182
|
+
"让代码更有温度",
|
|
183
|
+
"民生所系,支付所向",
|
|
184
|
+
"智慧风控,安全同行",
|
|
185
|
+
"绿色支付,低碳生活",
|
|
168
186
|
"保持热爱,奔赴山海",
|
|
169
187
|
"每一行代码都是故事",
|
|
170
188
|
"技术只是手段,创意才是灵魂",
|
|
@@ -198,8 +216,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
198
216
|
// ── Plan mode segment ────────────────────────────────────
|
|
199
217
|
if (planModeEnabled) {
|
|
200
218
|
segments.push(theme.fg("success", "⏸ 计划模式"));
|
|
201
|
-
}else{
|
|
202
|
-
|
|
219
|
+
} else {
|
|
220
|
+
segments.push(theme.fg("error", "▶ 自动模式"));
|
|
203
221
|
}
|
|
204
222
|
|
|
205
223
|
segments.push(theme.fg("muted", quote));
|