@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.
Files changed (33) hide show
  1. package/bin/cli.js +92 -68
  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 +622 -180
  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 +0 -5
  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/dist/banner.d.ts.map +1 -1
  20. package/dist/banner.js +8 -1
  21. package/dist/banner.js.map +1 -1
  22. package/dist/config-loader.d.ts.map +1 -1
  23. package/dist/config-loader.js +9 -2
  24. package/dist/config-loader.js.map +1 -1
  25. package/package.json +6 -7
  26. package/scripts/regression/results/.last_pid +1 -1
  27. package/scripts/regression/results/.last_workdir +1 -1
  28. package/scripts/regression/results/02-extension-loading.log +16 -27
  29. package/scripts/regression/results/03-prompt-stream.log +14 -23
  30. package/scripts/regression/results/03-rpc-protocol.log +20 -31
  31. package/scripts/regression/results/04-slash-commands-and-skills.log +11 -10
  32. package/scripts/regression/results/05-tool-execution.log +6 -4
  33. package/scripts/regression/results/06-pi-new-features.log +22 -26
@@ -21,7 +21,11 @@
21
21
  import * as fs from "node:fs";
22
22
  import * as os from "node:os";
23
23
  import * as path from "node:path";
24
- import { SessionManager, type ExtensionAPI, type ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
24
+ import {
25
+ SessionManager,
26
+ type ExtensionAPI,
27
+ type ExtensionCommandContext,
28
+ } from "@earendil-works/pi-coding-agent";
25
29
  import type { AutocompleteItem } from "@earendil-works/pi-tui";
26
30
 
27
31
  // ============== 类型定义 ==============
@@ -51,9 +55,7 @@ type CdSuggestion = {
51
55
  };
52
56
 
53
57
  // ============== 常量 ==============
54
-
55
58
  const STORE_VERSION = 1;
56
- const MAX_HISTORY_ENTRIES = 200;
57
59
  const MAX_SUGGESTIONS = 24;
58
60
  const MAX_CHILDREN_SCANNED = 300;
59
61
  const STATUS_KEY = "cd-history";
@@ -75,12 +77,16 @@ const RESERVED_ALIAS_NAMES = new Set([
75
77
 
76
78
  function getAgentDir(): string {
77
79
  const configured = process.env.PI_CODING_AGENT_DIR?.trim();
78
- return configured ? path.resolve(expandTilde(configured)) : path.join(os.homedir(), ".aicode-cli", "agent");
80
+ return configured
81
+ ? path.resolve(expandTilde(configured))
82
+ : path.join(os.homedir(), ".aicode-cli", "agent");
79
83
  }
80
84
 
81
85
  function getStorePath(): string {
82
86
  const configured = process.env.PI_CD_HISTORY_STORE_PATH?.trim();
83
- return configured ? path.resolve(expandTilde(configured)) : path.join(getAgentDir(), "state", "cd-history.json");
87
+ return configured
88
+ ? path.resolve(expandTilde(configured))
89
+ : path.join(getAgentDir(), "state", "cd-history.json");
84
90
  }
85
91
 
86
92
  function emptyStore(): CdStore {
@@ -113,7 +119,10 @@ function isDirectory(input: string): boolean {
113
119
  }
114
120
  }
115
121
 
116
- function normalizeDirectory(input: string, baseDir?: string): string | undefined {
122
+ function normalizeDirectory(
123
+ input: string,
124
+ baseDir?: string,
125
+ ): string | undefined {
117
126
  const resolved = canonicalPath(input, baseDir);
118
127
  return isDirectory(resolved) ? resolved : undefined;
119
128
  }
@@ -142,9 +151,18 @@ function formatTimestamp(ts: number): string {
142
151
 
143
152
  function parseTimestamp(input: string): number {
144
153
  // 支持 "YYYY-MM-DD HH:mm:ss" 格式
145
- const match = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})$/.exec(input);
154
+ const match = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})$/.exec(
155
+ input,
156
+ );
146
157
  if (match) {
147
- return new Date(+match[1], +match[2] - 1, +match[3], +match[4], +match[5], +match[6]).getTime();
158
+ return new Date(
159
+ +match[1],
160
+ +match[2] - 1,
161
+ +match[3],
162
+ +match[4],
163
+ +match[5],
164
+ +match[6],
165
+ ).getTime();
148
166
  }
149
167
  const fallback = Date.parse(input);
150
168
  return Number.isFinite(fallback) ? fallback : Date.now();
@@ -176,7 +194,11 @@ function readStore(storePath: string): CdStore {
176
194
 
177
195
  const store = emptyStore();
178
196
  const rawAliases = (parsed as { aliases?: unknown }).aliases;
179
- if (rawAliases && typeof rawAliases === "object" && !Array.isArray(rawAliases)) {
197
+ if (
198
+ rawAliases &&
199
+ typeof rawAliases === "object" &&
200
+ !Array.isArray(rawAliases)
201
+ ) {
180
202
  for (const [name, target] of Object.entries(rawAliases)) {
181
203
  if (!isValidAliasName(name) || typeof target !== "string") continue;
182
204
  const normalized = normalizeDirectory(target);
@@ -188,14 +210,37 @@ function readStore(storePath: string): CdStore {
188
210
  if (Array.isArray(rawHistory)) {
189
211
  for (const item of rawHistory) {
190
212
  if (!item || typeof item !== "object") continue;
191
- const record = item as { path?: unknown; uses?: unknown; firstUsed?: unknown; lastUsed?: unknown };
213
+ const record = item as {
214
+ path?: unknown;
215
+ uses?: unknown;
216
+ firstUsed?: unknown;
217
+ lastUsed?: unknown;
218
+ };
192
219
  if (typeof record.path !== "string") continue;
193
220
  const normalized = normalizeDirectory(record.path);
194
221
  if (!normalized) continue;
195
- const uses = typeof record.uses === "number" && Number.isFinite(record.uses) ? Math.max(1, Math.floor(record.uses)) : 1;
196
- const firstUsedRaw = typeof record.firstUsed === "string" ? parseTimestamp(record.firstUsed) : typeof record.firstUsed === "number" ? record.firstUsed : Date.now();
197
- const lastUsedRaw = typeof record.lastUsed === "string" ? parseTimestamp(record.lastUsed) : typeof record.lastUsed === "number" ? record.lastUsed : firstUsedRaw;
198
- store.history.push({ path: normalized, uses, firstUsed: formatTimestamp(firstUsedRaw), lastUsed: formatTimestamp(lastUsedRaw) });
222
+ const uses =
223
+ typeof record.uses === "number" && Number.isFinite(record.uses)
224
+ ? Math.max(1, Math.floor(record.uses))
225
+ : 1;
226
+ const firstUsedRaw =
227
+ typeof record.firstUsed === "string"
228
+ ? parseTimestamp(record.firstUsed)
229
+ : typeof record.firstUsed === "number"
230
+ ? record.firstUsed
231
+ : Date.now();
232
+ const lastUsedRaw =
233
+ typeof record.lastUsed === "string"
234
+ ? parseTimestamp(record.lastUsed)
235
+ : typeof record.lastUsed === "number"
236
+ ? record.lastUsed
237
+ : firstUsedRaw;
238
+ store.history.push({
239
+ path: normalized,
240
+ uses,
241
+ firstUsed: formatTimestamp(firstUsedRaw),
242
+ lastUsed: formatTimestamp(lastUsedRaw),
243
+ });
199
244
  }
200
245
  }
201
246
 
@@ -210,7 +255,11 @@ function writeStore(storePath: string, store: CdStore): void {
210
255
  fs.mkdirSync(path.dirname(storePath), { recursive: true });
211
256
  const compacted = compactStore(store);
212
257
  const tempPath = `${storePath}.${process.pid}.${Date.now()}.tmp`;
213
- fs.writeFileSync(tempPath, `${JSON.stringify(compacted, null, 2)}\n`, "utf8");
258
+ fs.writeFileSync(
259
+ tempPath,
260
+ `${JSON.stringify(compacted, null, 2)}\n`,
261
+ "utf8",
262
+ );
214
263
  fs.renameSync(tempPath, storePath);
215
264
  } catch {
216
265
  // Directory changes should still work if persistence fails.
@@ -244,8 +293,13 @@ function compactStore(store: CdStore): CdStore {
244
293
  }
245
294
 
246
295
  const history = Array.from(byPath.values())
247
- .sort((a, b) => b.lastUsed - a.lastUsed || b.uses - a.uses || a.path.localeCompare(b.path))
248
- .slice(0, MAX_HISTORY_ENTRIES);
296
+ // pi 1.1.0: lastUsed/firstUsed 是 ISO 字符串, 用 localeCompare 比较
297
+ .sort(
298
+ (a, b) =>
299
+ b.lastUsed.localeCompare(a.lastUsed) ||
300
+ b.uses - a.uses ||
301
+ a.path.localeCompare(b.path),
302
+ );
249
303
 
250
304
  return { version: STORE_VERSION, aliases, history };
251
305
  }
@@ -261,7 +315,12 @@ function recordVisit(store: CdStore, targetPath: string): CdStore {
261
315
  existing.uses += 1;
262
316
  existing.lastUsed = formatTimestamp(now);
263
317
  } else {
264
- next.history.unshift({ path: normalized, uses: 1, firstUsed: formatTimestamp(now), lastUsed: formatTimestamp(now) });
318
+ next.history.unshift({
319
+ path: normalized,
320
+ uses: 1,
321
+ firstUsed: formatTimestamp(now),
322
+ lastUsed: formatTimestamp(now),
323
+ });
265
324
  }
266
325
  return compactStore(next);
267
326
  }
@@ -343,20 +402,35 @@ function matchScore(suggestion: CdSuggestion, query: string): number {
343
402
  if (!trimmed) return suggestion.score;
344
403
 
345
404
  const basename = path.basename(suggestion.targetPath).toLowerCase();
346
- const fields = [suggestion.value, suggestion.label, basename, formatPath(suggestion.targetPath), suggestion.targetPath].map((value) => value.toLowerCase());
405
+ const fields = [
406
+ suggestion.value,
407
+ suggestion.label,
408
+ basename,
409
+ formatPath(suggestion.targetPath),
410
+ suggestion.targetPath,
411
+ ].map((value) => value.toLowerCase());
347
412
 
348
413
  if (fields.some((field) => field === trimmed)) return suggestion.score + 3000;
349
- if (suggestion.value.toLowerCase().startsWith(trimmed)) return suggestion.score + 2200;
414
+ if (suggestion.value.toLowerCase().startsWith(trimmed))
415
+ return suggestion.score + 2200;
350
416
  if (basename.startsWith(trimmed)) return suggestion.score + 1800;
351
- if (fields.some((field) => field.includes(trimmed))) return suggestion.score + 1200;
417
+ if (fields.some((field) => field.includes(trimmed)))
418
+ return suggestion.score + 1200;
352
419
 
353
- const bestFuzzy = Math.max(...fields.map((field) => fuzzyScore(field, trimmed)));
354
- return Number.isFinite(bestFuzzy) ? suggestion.score + bestFuzzy : Number.NEGATIVE_INFINITY;
420
+ const bestFuzzy = Math.max(
421
+ ...fields.map((field) => fuzzyScore(field, trimmed)),
422
+ );
423
+ return Number.isFinite(bestFuzzy)
424
+ ? suggestion.score + bestFuzzy
425
+ : Number.NEGATIVE_INFINITY;
355
426
  }
356
427
 
357
428
  // ============== 建议生成 ==============
358
429
 
359
- function addSuggestion(map: Map<string, CdSuggestion>, suggestion: CdSuggestion): void {
430
+ function addSuggestion(
431
+ map: Map<string, CdSuggestion>,
432
+ suggestion: CdSuggestion,
433
+ ): void {
360
434
  const key = `${suggestion.source}:${suggestion.value}:${suggestion.targetPath}`;
361
435
  const existing = map.get(key);
362
436
  if (!existing || suggestion.score > existing.score) map.set(key, suggestion);
@@ -387,7 +461,11 @@ function readChildDirectories(cwd: string): CdSuggestion[] {
387
461
  });
388
462
  }
389
463
 
390
- function buildSuggestions(cwd: string, store: CdStore, query = ""): CdSuggestion[] {
464
+ function buildSuggestions(
465
+ cwd: string,
466
+ store: CdStore,
467
+ query = "",
468
+ ): CdSuggestion[] {
391
469
  const suggestions = new Map<string, CdSuggestion>();
392
470
  const normalizedCwd = canonicalPath(cwd);
393
471
  const parent = path.dirname(normalizedCwd);
@@ -449,7 +527,11 @@ function buildSuggestions(cwd: string, store: CdStore, query = ""): CdSuggestion
449
527
  return Array.from(suggestions.values())
450
528
  .map((suggestion) => ({ suggestion, score: matchScore(suggestion, query) }))
451
529
  .filter((entry) => Number.isFinite(entry.score))
452
- .sort((a, b) => b.score - a.score || a.suggestion.label.localeCompare(b.suggestion.label))
530
+ .sort(
531
+ (a, b) =>
532
+ b.score - a.score ||
533
+ a.suggestion.label.localeCompare(b.suggestion.label),
534
+ )
453
535
  .slice(0, MAX_SUGGESTIONS)
454
536
  .map((entry) => entry.suggestion);
455
537
  }
@@ -459,7 +541,9 @@ function quoteArg(value: string): string {
459
541
  return `"${value.replace(/["\\]/g, (match) => `\\${match}`)}"`;
460
542
  }
461
543
 
462
- function suggestionsToAutocompleteItems(suggestions: CdSuggestion[]): AutocompleteItem[] {
544
+ function suggestionsToAutocompleteItems(
545
+ suggestions: CdSuggestion[],
546
+ ): AutocompleteItem[] {
463
547
  return suggestions.map((suggestion) => ({
464
548
  value: suggestion.value,
465
549
  label: suggestion.label,
@@ -467,9 +551,14 @@ function suggestionsToAutocompleteItems(suggestions: CdSuggestion[]): Autocomple
467
551
  }));
468
552
  }
469
553
 
470
- function resolveTarget(rawArgs: string, cwd: string, store: CdStore): { targetPath?: string; suggestions: CdSuggestion[] } {
554
+ function resolveTarget(
555
+ rawArgs: string,
556
+ cwd: string,
557
+ store: CdStore,
558
+ ): { targetPath?: string; suggestions: CdSuggestion[] } {
471
559
  const target = rawArgs.trim();
472
- if (!target || target === ".") return { targetPath: canonicalPath(cwd), suggestions: [] };
560
+ if (!target || target === ".")
561
+ return { targetPath: canonicalPath(cwd), suggestions: [] };
473
562
 
474
563
  // 别名匹配
475
564
  const aliasTarget = store.aliases[target];
@@ -500,9 +589,15 @@ function resolveTarget(rawArgs: string, cwd: string, store: CdStore): { targetPa
500
589
  function ensureEmptySessionFile(targetPath: string): string {
501
590
  const sessionManager = SessionManager.create(targetPath);
502
591
  const sessionPath = sessionManager.getSessionFile();
503
- if (!sessionPath) throw new Error("Unable to create a persistent aicode session for target directory");
504
-
505
- const internal = sessionManager as unknown as { fileEntries?: unknown[]; _rewriteFile?: () => void };
592
+ if (!sessionPath)
593
+ throw new Error(
594
+ "Unable to create a persistent aicode session for target directory",
595
+ );
596
+
597
+ const internal = sessionManager as unknown as {
598
+ fileEntries?: unknown[];
599
+ _rewriteFile?: () => void;
600
+ };
506
601
  if (typeof internal._rewriteFile === "function") {
507
602
  internal._rewriteFile();
508
603
  return sessionPath;
@@ -513,11 +608,18 @@ function ensureEmptySessionFile(targetPath: string): string {
513
608
  throw new Error("Unable to initialize target aicode session header");
514
609
  }
515
610
  fs.mkdirSync(path.dirname(sessionPath), { recursive: true });
516
- fs.writeFileSync(sessionPath, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, { flag: "wx" });
611
+ fs.writeFileSync(
612
+ sessionPath,
613
+ `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`,
614
+ { flag: "wx" },
615
+ );
517
616
  return sessionPath;
518
617
  }
519
618
 
520
- function createTargetSession(currentSessionPath: string | undefined, targetPath: string): string {
619
+ function createTargetSession(
620
+ currentSessionPath: string | undefined,
621
+ targetPath: string,
622
+ ): string {
521
623
  if (currentSessionPath && fs.existsSync(currentSessionPath)) {
522
624
  try {
523
625
  const forked = SessionManager.forkFrom(currentSessionPath, targetPath);
@@ -533,11 +635,22 @@ function createTargetSession(currentSessionPath: string | undefined, targetPath:
533
635
 
534
636
  // ============== UI 交互 ==============
535
637
 
536
- async function selectTarget(ctx: ExtensionCommandContext, suggestions: CdSuggestion[], title: string): Promise<CdSuggestion | undefined> {
638
+ async function selectTarget(
639
+ ctx: ExtensionCommandContext,
640
+ suggestions: CdSuggestion[],
641
+ title: string,
642
+ ): Promise<CdSuggestion | undefined> {
537
643
  if (!ctx.hasUI || suggestions.length === 0) return undefined;
538
644
 
539
645
  const items = suggestions.map((suggestion, index) => {
540
- const prefix = suggestion.source === "alias" ? "★" : suggestion.source === "history" ? "↺" : suggestion.source === "special" ? "•" : "dir";
646
+ const prefix =
647
+ suggestion.source === "alias"
648
+ ? "★"
649
+ : suggestion.source === "history"
650
+ ? "↺"
651
+ : suggestion.source === "special"
652
+ ? "•"
653
+ : "dir";
541
654
  return `${prefix} ${suggestion.label} — ${suggestion.description} [${index + 1}]`;
542
655
  });
543
656
  const selected = await ctx.ui.select(title, items);
@@ -565,7 +678,7 @@ function helpText(): string {
565
678
  * 参考 Claude Code /cd 的行为:新目录的 CLAUDE.md / AGENTS.md /AICODE.md 作为消息注入
566
679
  */
567
680
  function readProjectConfig(targetPath: string): string | null {
568
- const candidates = ["CLAUDE.md", "AGENTS.md","AICODE.md"];
681
+ const candidates = ["CLAUDE.md", "AGENTS.md", "AICODE.md"];
569
682
  const parts: string[] = [];
570
683
 
571
684
  for (const name of candidates) {
@@ -611,7 +724,10 @@ async function changeDirectory(
611
724
  const currentSessionPath = ctx.sessionManager.getSessionFile();
612
725
  let targetSessionPath: string;
613
726
  try {
614
- targetSessionPath = createTargetSession(currentSessionPath, normalizedTarget);
727
+ targetSessionPath = createTargetSession(
728
+ currentSessionPath,
729
+ normalizedTarget,
730
+ );
615
731
  } catch (error) {
616
732
  const message = error instanceof Error ? error.message : String(error);
617
733
  ctx.ui.notify(`cd: failed to create target session: ${message}`, "error");
@@ -648,11 +764,16 @@ async function changeDirectory(
648
764
 
649
765
  function addAlias(store: CdStore, name: string, targetPath: string): CdStore {
650
766
  if (!isValidAliasName(name)) {
651
- throw new Error(`Invalid alias '${name}'. Use letters, numbers, dot, underscore, or dash; start with a letter/number.`);
767
+ throw new Error(
768
+ `Invalid alias '${name}'. Use letters, numbers, dot, underscore, or dash; start with a letter/number.`,
769
+ );
652
770
  }
653
771
  const normalized = normalizeDirectory(targetPath);
654
772
  if (!normalized) throw new Error(`Not a directory: ${targetPath}`);
655
- return compactStore({ ...store, aliases: { ...store.aliases, [name]: normalized } });
773
+ return compactStore({
774
+ ...store,
775
+ aliases: { ...store.aliases, [name]: normalized },
776
+ });
656
777
  }
657
778
 
658
779
  function removeAlias(store: CdStore, name: string): CdStore {
@@ -686,17 +807,33 @@ export default function cdHistoryExtension(pi: ExtensionAPI) {
686
807
 
687
808
  // 注册 /cd 命令
688
809
  pi.registerCommand("cd", {
689
- description: "Change aicode-cli working directory with ranked history and aliases",
810
+ description:
811
+ "Change aicode-cli working directory with ranked history and aliases",
690
812
  getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
691
813
  refreshStore();
692
814
  const trimmed = prefix.trimStart();
693
815
  const words = splitArgs(trimmed);
694
- const commandOptions = ["--add", "--remove", "--list", "--status", "--clear-history", "--help"];
816
+ const commandOptions = [
817
+ "--add",
818
+ "--remove",
819
+ "--list",
820
+ "--status",
821
+ "--clear-history",
822
+ "--help",
823
+ ];
695
824
 
696
825
  // 选项补全
697
- if (trimmed.startsWith("--") && words.length <= 1 && !trimmed.endsWith(" ")) {
698
- const filtered = commandOptions.filter((option) => option.startsWith(trimmed));
699
- return filtered.length > 0 ? filtered.map((option) => ({ value: option, label: option })) : null;
826
+ if (
827
+ trimmed.startsWith("--") &&
828
+ words.length <= 1 &&
829
+ !trimmed.endsWith(" ")
830
+ ) {
831
+ const filtered = commandOptions.filter((option) =>
832
+ option.startsWith(trimmed),
833
+ );
834
+ return filtered.length > 0
835
+ ? filtered.map((option) => ({ value: option, label: option }))
836
+ : null;
700
837
  }
701
838
 
702
839
  // --remove 别名补全
@@ -705,24 +842,36 @@ export default function cdHistoryExtension(pi: ExtensionAPI) {
705
842
  const partial = words[1] ?? "";
706
843
  const items = Object.entries(store.aliases)
707
844
  .filter(([name]) => name.startsWith(partial))
708
- .map(([name, target]) => ({ value: `${option} ${name}`, label: name, description: formatPath(target) }));
845
+ .map(([name, target]) => ({
846
+ value: `${option} ${name}`,
847
+ label: name,
848
+ description: formatPath(target),
849
+ }));
709
850
  return items.length > 0 ? items : null;
710
851
  }
711
852
 
712
853
  // --add 路径补全
713
- if (words[0] === "--add" && words[1] && (trimmed.endsWith(" ") || words.length >= 3)) {
854
+ if (
855
+ words[0] === "--add" &&
856
+ words[1] &&
857
+ (trimmed.endsWith(" ") || words.length >= 3)
858
+ ) {
714
859
  const aliasName = words[1];
715
860
  const pathPrefix = words.length >= 3 ? words.slice(2).join(" ") : "";
716
- const items = buildSuggestions(currentCwd, store, pathPrefix).map((suggestion) => ({
717
- value: `--add ${aliasName} ${quoteArg(formatPath(suggestion.targetPath))}`,
718
- label: suggestion.label,
719
- description: suggestion.description,
720
- }));
861
+ const items = buildSuggestions(currentCwd, store, pathPrefix).map(
862
+ (suggestion) => ({
863
+ value: `--add ${aliasName} ${quoteArg(formatPath(suggestion.targetPath))}`,
864
+ label: suggestion.label,
865
+ description: suggestion.description,
866
+ }),
867
+ );
721
868
  return items.length > 0 ? items : null;
722
869
  }
723
870
 
724
871
  // 普通路径补全
725
- const items = suggestionsToAutocompleteItems(buildSuggestions(currentCwd, store, prefix));
872
+ const items = suggestionsToAutocompleteItems(
873
+ buildSuggestions(currentCwd, store, prefix),
874
+ );
726
875
  return items.length > 0 ? items : null;
727
876
  },
728
877
  handler: async (args, ctx) => {
@@ -747,7 +896,9 @@ export default function cdHistoryExtension(pi: ExtensionAPI) {
747
896
 
748
897
  // 清除历史
749
898
  if (trimmed === "--clear-history") {
750
- const ok = !ctx.hasUI || await ctx.ui.confirm("Clear /cd history?", "Aliases will be kept.");
899
+ const ok =
900
+ !ctx.hasUI ||
901
+ (await ctx.ui.confirm("Clear /cd history?", "Aliases will be kept."));
751
902
  if (!ok) return;
752
903
  setStore({ ...store, history: [] });
753
904
  ctx.ui.notify("/cd history cleared", "info");
@@ -757,8 +908,18 @@ export default function cdHistoryExtension(pi: ExtensionAPI) {
757
908
  // 列表选择
758
909
  if (trimmed === "--list") {
759
910
  const suggestions = buildSuggestions(currentCwd, store);
760
- const selected = await selectTarget(ctx, suggestions, "/cd aliases and history");
761
- if (selected) await changeDirectory(ctx, selected.targetPath, () => store, setStore);
911
+ const selected = await selectTarget(
912
+ ctx,
913
+ suggestions,
914
+ "/cd aliases and history",
915
+ );
916
+ if (selected)
917
+ await changeDirectory(
918
+ ctx,
919
+ selected.targetPath,
920
+ () => store,
921
+ setStore,
922
+ );
762
923
  return;
763
924
  }
764
925
 
@@ -771,12 +932,17 @@ export default function cdHistoryExtension(pi: ExtensionAPI) {
771
932
  ctx.ui.notify("Usage: /cd --add <name> [dir]", "error");
772
933
  return;
773
934
  }
774
- const rawTarget = words.length >= 3 ? words.slice(2).join(" ") : currentCwd;
935
+ const rawTarget =
936
+ words.length >= 3 ? words.slice(2).join(" ") : currentCwd;
775
937
  try {
776
938
  setStore(addAlias(store, name, rawTarget));
777
- ctx.ui.notify(`Added /cd ${name} → ${formatPath(store.aliases[name] ?? normalizeDirectory(rawTarget, currentCwd) ?? rawTarget)}`, "info");
939
+ ctx.ui.notify(
940
+ `Added /cd ${name} → ${formatPath(store.aliases[name] ?? normalizeDirectory(rawTarget, currentCwd) ?? rawTarget)}`,
941
+ "info",
942
+ );
778
943
  } catch (error) {
779
- const message = error instanceof Error ? error.message : String(error);
944
+ const message =
945
+ error instanceof Error ? error.message : String(error);
780
946
  ctx.ui.notify(`cd alias failed: ${message}`, "error");
781
947
  }
782
948
  return;
@@ -800,8 +966,18 @@ export default function cdHistoryExtension(pi: ExtensionAPI) {
800
966
 
801
967
  // 无参数:显示选择列表
802
968
  if (!trimmed) {
803
- const selected = await selectTarget(ctx, buildSuggestions(currentCwd, store), "Change directory");
804
- if (selected) await changeDirectory(ctx, selected.targetPath, () => store, setStore);
969
+ const selected = await selectTarget(
970
+ ctx,
971
+ buildSuggestions(currentCwd, store),
972
+ "Change directory",
973
+ );
974
+ if (selected)
975
+ await changeDirectory(
976
+ ctx,
977
+ selected.targetPath,
978
+ () => store,
979
+ setStore,
980
+ );
805
981
  return;
806
982
  }
807
983
 
@@ -813,7 +989,11 @@ export default function cdHistoryExtension(pi: ExtensionAPI) {
813
989
  }
814
990
 
815
991
  // 没有精确匹配:显示建议
816
- const selected = await selectTarget(ctx, resolved.suggestions, `No exact directory for '${trimmed}'. Pick one:`);
992
+ const selected = await selectTarget(
993
+ ctx,
994
+ resolved.suggestions,
995
+ `No exact directory for '${trimmed}'. Pick one:`,
996
+ );
817
997
  if (selected) {
818
998
  await changeDirectory(ctx, selected.targetPath, () => store, setStore);
819
999
  return;
@@ -828,7 +1008,10 @@ export default function cdHistoryExtension(pi: ExtensionAPI) {
828
1008
  description: "Reload /cd history and alias store",
829
1009
  handler: async (_args, ctx) => {
830
1010
  refreshStore();
831
- ctx.ui.notify(`Reloaded /cd store (${Object.keys(store.aliases).length} aliases, ${store.history.length} history entries)`, "info");
1011
+ ctx.ui.notify(
1012
+ `Reloaded /cd store (${Object.keys(store.aliases).length} aliases, ${store.history.length} history entries)`,
1013
+ "info",
1014
+ );
832
1015
  },
833
1016
  });
834
1017
  }
@@ -266,16 +266,6 @@ function getNextFrame(): string[] {
266
266
 
267
267
  return frame;
268
268
  }
269
-
270
- function showPetNotification(ctx: ExtensionAPI["on"] extends (event: string, cb: (event: any, ctx: infer Ctx) => void) => void ? Ctx : never) {
271
- const pet = PETS[currentPet]!;
272
- const hatNames = getHatNames();
273
- let msg = `${pet.emoji} 本次宠物:${pet.name}`;
274
- if (isShiny) msg += " ✨(闪亮)";
275
- if (currentHat !== "none") msg += ` + ${hatNames[currentHat]}`;
276
- ctx.ui.notify(msg, "info");
277
- }
278
-
279
269
  function startPetAnimation(ctx: any) {
280
270
  const pet = PETS[currentPet]!;
281
271
 
@@ -305,7 +295,6 @@ export default function (pi: ExtensionAPI) {
305
295
  // 注册 /pet 命令
306
296
  pi.registerCommand("pet", {
307
297
  description: "切换随机宠物",
308
- usage: "/pet",
309
298
  handler: async (_args, ctx) => {
310
299
  // 清理旧动画
311
300
  if (intervalId) {
@@ -757,6 +757,8 @@ export default function aicodePlan(pi: ExtensionAPI) {
757
757
  };
758
758
  }
759
759
  }
760
+ // 默认: 不拦截
761
+ return;
760
762
  });
761
763
 
762
764
  // agent 启动前注入系统提示词
@@ -463,6 +463,7 @@ export default function safetyGuard(pi: ExtensionAPI) {
463
463
  return;
464
464
  }
465
465
  if (ctx.hasUI) ctx.ui.notify("Usage: /safety-guard on|off|status|allow-list|allow-clear-session|allow-clear-permanent", "warning");
466
+ return;
466
467
  },
467
468
  });
468
469
 
@@ -548,5 +549,7 @@ export default function safetyGuard(pi: ExtensionAPI) {
548
549
  if (decision && "block" in decision) return decision;
549
550
  return;
550
551
  }
552
+ // 其他工具不拦截
553
+ return;
551
554
  });
552
555
  }
@@ -6,8 +6,7 @@
6
6
  * 技巧列表包含快捷键、命令使用及联系方式提示
7
7
  */
8
8
 
9
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
10
-
9
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
10
  const AICODE_TIPS = [
12
11
  "@ 快速搜索项目文件",
13
12
  "Ctrl+L 快速切换备选模型",
@@ -184,12 +183,6 @@ export default function (pi: ExtensionAPI) {
184
183
  const b = Math.max(0, Math.min(255, baseB + Math.floor(Math.random() * offset * 2 - offset)));
185
184
  return `\x1b[38;2;${r};${g};${b}m`;
186
185
  };
187
-
188
- const hideTip = () => {
189
- if (!currentUI) return;
190
- currentUI.setWidget("aicode-tips", [], { placement: "aboveEditor" });
191
- };
192
-
193
186
  const clearAllTimers = () => {
194
187
  if (autoHideTimeoutId) {
195
188
  clearTimeout(autoHideTimeoutId);
@@ -201,14 +194,6 @@ export default function (pi: ExtensionAPI) {
201
194
  }
202
195
  };
203
196
 
204
- const padRight = (str: string, width: number): string => {
205
- const chinese = (str.match(/[\u4e00-\u9fff]/g) || []).length;
206
- const ascii = str.length - chinese;
207
- const fullWidth = chinese * 2 + ascii;
208
- const padding = Math.max(0, width - fullWidth);
209
- return str + " ".repeat(padding);
210
- };
211
-
212
197
  const showRandomTip = (isInternalCall = false) => {
213
198
  if (!currentUI) return;
214
199