@zhuxixi/pi-agent-board 0.3.1 → 0.4.1

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.
@@ -21,6 +21,8 @@ export const rosterPath = (root) => path.join(root, "roster.json");
21
21
  export const launchPrefsPath = (root) => path.join(root, "launch-prefs.json");
22
22
  /** @param {string} root */
23
23
  export const gcHistoryPath = (root) => path.join(root, "gc-history.jsonl");
24
+ /** @param {string} root */
25
+ export const cwdStatsPath = (root) => path.join(root, "cwd-stats.json");
24
26
 
25
27
  /** @param {string} root */
26
28
  export const viewsDir = (root) => path.join(root, "views");
@@ -763,6 +763,10 @@ export function createService(opts) {
763
763
  return { ok: true, viewId: meta.id, reused: false };
764
764
  },
765
765
 
766
+ getRoot() {
767
+ return root;
768
+ },
769
+
766
770
  getLaunchPrefs() {
767
771
  return readLaunchPrefs(root);
768
772
  },
@@ -17,11 +17,14 @@ import {
17
17
  canonicalModelRef,
18
18
  clampThinkingLevel,
19
19
  listDirectorySuggestions,
20
+ existingCwdCandidates,
21
+ nextCwdPickerState,
20
22
  resolveDirectoryValue,
21
23
  resolveLaunchContext,
22
24
  supportedThinkingLevels,
23
25
  } from "../core/launch-options.mjs";
24
26
  import { createPrewarmScheduler } from "../core/prewarm-schedule.mjs";
27
+ import { ensureCwdStatsSeeded, rankedCwdCandidates, recordCwdLaunch } from "../core/cwd-stats.mjs";
25
28
  import { filterRows, groupRowsByFolder, rowState, stateGlyph } from "../core/rows.mjs";
26
29
  import { loadSessionView } from "../core/session-view.mjs";
27
30
  import { GROUP_LABELS } from "../core/types.mjs";
@@ -81,6 +84,11 @@ interface InputNotice {
81
84
  expiresAt: number;
82
85
  }
83
86
 
87
+ interface CwdCandidate {
88
+ path: string;
89
+ count: number;
90
+ }
91
+
84
92
  interface LaunchState {
85
93
  fieldIndex: number;
86
94
  picker: LaunchPicker;
@@ -89,6 +97,8 @@ interface LaunchState {
89
97
  cwdQuery: string;
90
98
  cwdSuggestions: string[];
91
99
  cwdSuggestionIndex: number;
100
+ cwdRanked: CwdCandidate[];
101
+ cwdPickerMode: "favorites" | "browse";
92
102
  choices: LaunchChoice[];
93
103
  scopeSource: "scoped" | "all";
94
104
  model: LaunchModel | null;
@@ -102,6 +112,7 @@ interface LaunchState {
102
112
 
103
113
  export interface DashboardDeps {
104
114
  service: Service;
115
+ root: string;
105
116
  defaultCwd: string;
106
117
  availableModels: LaunchModel[];
107
118
  currentModel: LaunchModel | null;
@@ -562,11 +573,24 @@ export class DashboardComponent implements Component {
562
573
  launch.picker = null;
563
574
  return;
564
575
  }
576
+ if (launch.picker === "cwd" && matchesKey(data, Key.tab)) {
577
+ if (launch.cwdPickerMode === "favorites" && launch.cwdSuggestions.length > 0) {
578
+ const completed = launch.cwdSuggestions[launch.cwdSuggestionIndex] ?? launch.cwdSuggestions[0];
579
+ launch.cwdQuery = completed;
580
+ const state = nextCwdPickerState(launch.cwdQuery, launch.cwdRanked, launch.cwd);
581
+ launch.cwdPickerMode = state.mode;
582
+ launch.cwdSuggestions = state.suggestions;
583
+ launch.cwdSuggestionIndex = Math.max(0, state.suggestions.indexOf(completed));
584
+ }
585
+ return;
586
+ }
565
587
  if (launch.picker === "cwd") {
566
588
  const next = this.applyLaunchQueryInput(launch.cwdQuery, data);
567
589
  if (next !== null) {
568
590
  launch.cwdQuery = next;
569
- launch.cwdSuggestions = listDirectorySuggestions(next, launch.cwd);
591
+ const state = nextCwdPickerState(next, launch.cwdRanked, launch.cwd);
592
+ launch.cwdPickerMode = state.mode;
593
+ launch.cwdSuggestions = state.suggestions;
570
594
  launch.cwdSuggestionIndex = 0;
571
595
  }
572
596
  return;
@@ -611,6 +635,11 @@ export class DashboardComponent implements Component {
611
635
  const prompt = this.input.trim();
612
636
  if (!prompt) return this.toListMode();
613
637
  const defaults = this.launchDefaults();
638
+ try {
639
+ ensureCwdStatsSeeded(this.deps.root);
640
+ } catch {
641
+ /* best effort: favorites degrade to browse mode */
642
+ }
614
643
  this.launch = this.buildLaunchState(defaults.cwd, defaults.model, defaults.thinking);
615
644
  this.mode = "launch";
616
645
  this.inputNotice = null;
@@ -647,7 +676,6 @@ export class DashboardComponent implements Component {
647
676
  thinkingOptions: ThinkingLevel[];
648
677
  scopeSource: "scoped" | "all";
649
678
  };
650
- const initialBrowserCwd = homeLaunchRoot(cwd);
651
679
  const modelFiltered = filterLaunchChoices(context.choices, "");
652
680
  const modelIndex = Math.max(0, modelFiltered.findIndex((choice) => sameLaunchModel(choice.model, context.selectedModel)));
653
681
  const thinkingOptions = supportedThinkingLevels(context.selectedModel) as ThinkingLevel[];
@@ -658,9 +686,11 @@ export class DashboardComponent implements Component {
658
686
  picker: null,
659
687
  action: "background",
660
688
  cwd,
661
- cwdQuery: initialBrowserCwd,
662
- cwdSuggestions: listDirectorySuggestions(initialBrowserCwd, cwd),
689
+ cwdQuery: "",
690
+ cwdSuggestions: [],
663
691
  cwdSuggestionIndex: 0,
692
+ cwdRanked: [],
693
+ cwdPickerMode: "browse",
664
694
  choices: context.choices,
665
695
  scopeSource: context.scopeSource,
666
696
  model: context.selectedModel,
@@ -678,8 +708,16 @@ export class DashboardComponent implements Component {
678
708
  if (!launch) return;
679
709
  launch.picker = picker;
680
710
  if (picker === "cwd") {
681
- launch.cwdQuery = seed ?? launch.cwdQuery ?? homeLaunchRoot(launch.cwd);
682
- launch.cwdSuggestions = listDirectorySuggestions(launch.cwdQuery, launch.cwd);
711
+ try {
712
+ ensureCwdStatsSeeded(this.deps.root);
713
+ launch.cwdRanked = existingCwdCandidates(rankedCwdCandidates(this.deps.root, 8));
714
+ } catch {
715
+ launch.cwdRanked = [];
716
+ }
717
+ launch.cwdQuery = seed ?? "";
718
+ const state = nextCwdPickerState(launch.cwdQuery, launch.cwdRanked, launch.cwd);
719
+ launch.cwdPickerMode = state.mode;
720
+ launch.cwdSuggestions = state.suggestions;
683
721
  launch.cwdSuggestionIndex = 0;
684
722
  return;
685
723
  }
@@ -794,6 +832,11 @@ export class DashboardComponent implements Component {
794
832
  if (!res.ok) this.notice(res.error ?? "Dispatch failed", "error");
795
833
  else {
796
834
  this.lastLaunchPrefs = { ...this.deps.service.getLaunchPrefs?.(), cwd: launchCwd, model: launchModel, thinkingLevel: launchThinking };
835
+ try {
836
+ recordCwdLaunch(this.deps.root, launchCwd);
837
+ } catch {
838
+ /* best effort: stats must never block dispatch */
839
+ }
797
840
  try {
798
841
  this.deps.service.saveLaunchPrefs?.(this.lastLaunchPrefs);
799
842
  } catch {
@@ -1474,9 +1517,19 @@ export class DashboardComponent implements Component {
1474
1517
  lines.push("");
1475
1518
  if (launch.picker === "cwd") {
1476
1519
  lines.push(t.fg("warning", `cwd› ${singleLineInput(launch.cwdQuery)}${cursor()}`));
1477
- lines.push(...this.renderLaunchSuggestions(inner, launch.cwdSuggestions, launch.cwdSuggestionIndex, (value) => displayPath(value)));
1478
- lines.push("");
1479
- lines.push(t.fg("dim", "type to filter folders · enter choose · esc back"));
1520
+ if (launch.cwdPickerMode === "favorites") {
1521
+ const counts = new Map(launch.cwdRanked.map((entry) => [entry.path, entry.count]));
1522
+ lines.push(...this.renderLaunchSuggestions(inner, launch.cwdSuggestions, launch.cwdSuggestionIndex, (value) => {
1523
+ const count = counts.get(value) ?? 0;
1524
+ return `${displayPath(value)}${count > 0 ? ` ${count}×` : ""}`;
1525
+ }));
1526
+ lines.push("");
1527
+ lines.push(t.fg("dim", "常用目录 · type to search · tab complete · enter choose · esc back"));
1528
+ } else {
1529
+ lines.push(...this.renderLaunchSuggestions(inner, launch.cwdSuggestions, launch.cwdSuggestionIndex, (value) => displayPath(value)));
1530
+ lines.push("");
1531
+ lines.push(t.fg("dim", "type to filter folders · enter choose · esc back"));
1532
+ }
1480
1533
  } else if (launch.picker === "model") {
1481
1534
  lines.push(t.fg("warning", `model› ${singleLineInput(launch.modelQuery)}${cursor()}`));
1482
1535
  lines.push(...this.renderLaunchSuggestions(inner, launch.modelFiltered, launch.modelIndex, (choice) => `${formatLaunchModel(choice.model)}${choice.thinkingLevel ? ` · ${choice.thinkingLevel}` : ""}`));
@@ -1662,10 +1715,6 @@ function stripBracketedPaste(data: string): string {
1662
1715
  return data.replace(/\x1b\[200~|\x1b\[201~/g, "");
1663
1716
  }
1664
1717
 
1665
- function homeLaunchRoot(fallback: string): string {
1666
- return process.env.HOME || process.env.USERPROFILE ? "~" : fallback;
1667
- }
1668
-
1669
1718
  function renderCenteredBox(lines: string[], width: number, height: number, theme: ThemeLike): string[] {
1670
1719
  const innerWidth = Math.max(20, Math.min(width - 6, Math.max(...lines.map((line) => visibleWidth(line)), 20)));
1671
1720
  const boxWidth = Math.min(width, innerWidth + 4);