@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.
Files changed (144) hide show
  1. package/bin/cli.js +116 -74
  2. package/config/agent/extensions/aicode-advisor.ts +187 -64
  3. package/config/agent/extensions/aicode-banner.ts +101 -0
  4. package/config/agent/extensions/aicode-btw.ts +619 -182
  5. package/config/agent/extensions/aicode-cd.ts +246 -63
  6. package/config/agent/extensions/aicode-pets.ts +0 -11
  7. package/config/agent/extensions/aicode-plan.ts +2 -0
  8. package/config/agent/extensions/aicode-safety-guard.ts +3 -0
  9. package/config/agent/extensions/aicode-tips.ts +1 -16
  10. package/config/agent/extensions/context-monitor.ts +30 -15
  11. package/config/agent/extensions/context.ts +132 -60
  12. package/config/agent/extensions/custom-footer.ts +42 -24
  13. package/config/agent/extensions/custom.ts +41 -29
  14. package/config/agent/extensions/error-monitor.ts +37 -33
  15. package/config/agent/extensions/goal.ts +8 -6
  16. package/config/agent/extensions/msg-debug.ts +103 -112
  17. package/config/agent/extensions/review.ts +365 -164
  18. package/config/agent/extensions/working-msg.ts +2 -4
  19. package/config/agent/settings.json +15 -14
  20. package/config/agent/skills/hello/SKILL.md +1 -1
  21. package/config/agent/skills/refactor/SKILL.md +40 -0
  22. package/dist/banner.d.ts.map +1 -1
  23. package/dist/banner.js +8 -1
  24. package/dist/banner.js.map +1 -1
  25. package/dist/config-loader.d.ts.map +1 -1
  26. package/dist/config-loader.js +9 -2
  27. package/dist/config-loader.js.map +1 -1
  28. package/package.json +6 -7
  29. package/scripts/regression/README.md +224 -0
  30. package/scripts/regression/cases/01-version-and-deps.sh +94 -0
  31. package/scripts/regression/cases/02-extension-loading.sh +130 -0
  32. package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
  33. package/scripts/regression/cases/04-prompt-stream.sh +130 -0
  34. package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
  35. package/scripts/regression/cases/06-tool-execution.sh +115 -0
  36. package/scripts/regression/cases/07-pi-new-features.sh +141 -0
  37. package/scripts/regression/cases/08-cwd-resilience.sh +84 -0
  38. package/scripts/regression/lib/common.sh +287 -0
  39. package/scripts/regression/results/.last_pid +1 -0
  40. package/scripts/regression/results/.last_workdir +1 -0
  41. package/scripts/regression/results/02-extension-loading.log +19 -0
  42. package/scripts/regression/results/03-prompt-stream.log +16 -0
  43. package/scripts/regression/results/03-rpc-protocol.log +28 -0
  44. package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
  45. package/scripts/regression/results/05-tool-execution.log +8 -0
  46. package/scripts/regression/results/06-pi-new-features.log +27 -0
  47. package/scripts/regression/results/08-cwd-resilience.log +1 -0
  48. package/scripts/regression/run-all.sh +187 -0
  49. package/config/agent/bin/fd.exe +0 -0
  50. package/config/agent/bin/rg.exe +0 -0
  51. package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
  52. package/config/agent/extensions/aicode-snake.ts +0 -354
  53. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
  54. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
  55. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
  56. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
  57. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
  58. /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
  59. /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
  60. /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
  61. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
  62. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
  63. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
  64. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
  65. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
  66. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
  67. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
  68. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
  69. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  70. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
  71. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
  72. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
  73. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
  74. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
  75. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
  76. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
  77. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
  78. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
  79. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
  80. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
  81. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
  82. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
  83. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
  84. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
  85. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
  86. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
  87. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
  88. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
  89. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
  90. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
  91. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
  92. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
  93. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
  94. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
  95. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
  96. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
  97. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
  98. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
  99. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
  100. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
  101. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
  102. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
  103. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
  104. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
  105. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
  106. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
  107. /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
  108. /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
  109. /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
  110. /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
  111. /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
  112. /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
  113. /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
  114. /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
  115. /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
  116. /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
  117. /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
  118. /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
  119. /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
  120. /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
  121. /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
  122. /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
  123. /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
  124. /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
  125. /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
  126. /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
  127. /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
  128. /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
  129. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
  130. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
  131. /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
  132. /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
  133. /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
  134. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
  135. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
  136. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
  137. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
  138. /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
  139. /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
  140. /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
  141. /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
  142. /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
  143. /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
  144. /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 { ExtensionAPI, ExtensionContext, ToolResultEvent } from "@earendil-works/pi-coding-agent";
18
-
17
+ import type {
18
+ ExtensionAPI,
19
+ ExtensionContext,
20
+ } from "@earendil-works/pi-coding-agent";
19
21
  interface ContextThresholds {
20
- info: number; // 70%
22
+ info: number; // 70%
21
23
  warning: number; // 80%
22
- error: number; // 90%
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(percent: number): "normal" | "info" | "warning" | "error" | "critical" {
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): { level: "info" | "warning" | "error"; title: string; body: string } {
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 filledChar.repeat(filled) + emptyChar.repeat(empty) + ` ${percent.toFixed(1)}%`;
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(stats: ContextStats, ctx: ExtensionContext) {
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, ctx: ExtensionContext) => {
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 { ExtensionAPI, ExtensionCommandContext, ExtensionContext, ToolResultEvent } from "@earendil-works/pi-coding-agent";
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 { Container, Key, Text, matchesKey, type Component, type TUI } from "@earendil-works/pi-tui";
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(filePath: string): Promise<{ path: string; content: string; bytes: number } | null> {
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 { path: filePath, content: buf.toString("utf8"), bytes: buf.byteLength };
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(cwd: string): Promise<Array<{ path: string; tokens: number; bytes: number }>> {
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({ path: f.path, tokens: estimateTokens(f.content), bytes: f.bytes });
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
- const p = c.path ? normalizeReadPath(c.path, cwd) : "";
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
- let sys = toCols(parts.system);
229
- let tools = toCols(parts.tools);
230
- let con = toCols(parts.convo);
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(items: string[], renderItem: (item: string) => string, sep: string): string {
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
- // message-based context usage estimate from ctx.getContextUsage()
257
- messageTokens: number;
258
- contextWindow: number;
259
- // effective usage incl. a rough tool-definition estimate
260
- effectiveTokens: number;
261
- percent: number;
262
- remainingTokens: number;
263
- systemPromptTokens: number;
264
- agentTokens: number;
265
- toolsTokens: number;
266
- activeTools: number;
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")) + theme.fg("dim", " (Esc/q/Enter to close)"),
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(`~${u.effectiveTokens.toLocaleString()} / ${u.contextWindow.toLocaleString()}`) +
324
- muted(` (${u.percent.toFixed(1)}% used, ~${u.remainingTokens.toLocaleString()} left)`),
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(muted(`AGENTS (${this.data.agentFiles.length}): `) + text(this.data.agentFiles.length ? joinComma(this.data.agentFiles) : "(none)"));
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(muted(`Extensions (${this.data.extensions.length}): `) + text(this.data.extensions.length ? joinComma(this.data.extensions) : "(none)"));
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) => (loaded.has(name) ? this.theme.fg("success", name) : this.theme.fg("muted", 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 (absPath === s.skillFilePath || absPath.startsWith(s.skillDir + path.sep)) {
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, { name: skillName, path: abs });
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
- const p = c.path ?? "<unknown>";
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(pi.getAllTools().map((t) => [t.name, t] as const));
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 = ctxWindow > 0 ? Math.max(0, ctxWindow - effectiveTokens) : 0;
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(`System: ~${systemPromptTokens.toLocaleString()} tok (AGENTS ~${agentTokens.toLocaleString()})`);
538
- lines.push(`Tools: ~${toolsTokens.toLocaleString()} tok (${activeToolNames.length} active)`);
539
- lines.push(`AGENTS: ${agentFilePaths.length ? joinComma(agentFilePaths) : "(none)"}`);
540
- lines.push(`Extensions (${extensionFiles.length}): ${extensionFiles.length ? joinComma(extensionFiles) : "(none)"}`);
541
- lines.push(`Skills (${skills.length}): ${skills.length ? joinComma(skills) : "(none)"}`);
542
- lines.push(`Session: ${sessionUsage.totalTokens.toLocaleString()} tokens · ${formatUsd(sessionUsage.totalCost)}`);
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({ customType: "context", content: makePlainText(), display: true }, { triggerTurn: false });
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((a, b) => a.localeCompare(b));
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
- messageTokens,
557
- contextWindow: ctxWindow,
558
- effectiveTokens,
559
- percent,
560
- remainingTokens,
561
- systemPromptTokens,
562
- agentTokens,
563
- toolsTokens,
564
- activeTools: activeToolNames.length,
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: { totalTokens: sessionUsage.totalTokens, totalCost: sessionUsage.totalCost },
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
- return process.env.AICODE_CLI_VERSION || null;
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, 'utf-8'));
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
- const pwd = process.cwd();
72
- const projectName = pwd.split('/').pop() || "project";
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 = (statsRaw ? visibleWidth(statsRaw) + sepWidth : 0)
118
- + visibleWidth(ctxRaw) + sepWidth + visibleWidth(modelRaw);
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: "\x1b[38;2;51;45;62m", // surface1 — invisible
145
- minimal: "\x1b[38;2;67;61;78m", // surface2 — barely there
146
- low: "\x1b[38;2;166;227;161m", // green
147
- medium: "\x1b[38;2;137;180;250m", // blue
148
- high: "\x1b[38;2;250;179;135m", // peach
149
- xhigh: "\x1b[38;2;243;139;168m", // red
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
- segments.push(theme.fg("error", "▶ 自动模式"));
219
+ } else {
220
+ segments.push(theme.fg("error", "▶ 自动模式"));
203
221
  }
204
222
 
205
223
  segments.push(theme.fg("muted", quote));