@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.
- package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +916 -0
- package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +211 -0
- package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +131 -0
- package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +114 -0
- package/package.json +1 -1
- package/src/commands/agent-board.ts +1 -0
- package/src/core/cwd-stats.mjs +112 -0
- package/src/core/events.mjs +7 -3
- package/src/core/launch-options.mjs +47 -0
- package/src/core/paths.mjs +2 -0
- package/src/runtime/service.mjs +4 -0
- package/src/ui/dashboard.ts +62 -13
package/src/core/paths.mjs
CHANGED
|
@@ -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");
|
package/src/runtime/service.mjs
CHANGED
package/src/ui/dashboard.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
662
|
-
cwdSuggestions:
|
|
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
|
-
|
|
682
|
-
|
|
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
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
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);
|