pi-better-harness 0.3.4 → 0.3.5
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/node_modules/pi-better-background-tasks/package.json +1 -1
- package/node_modules/pi-better-background-tasks/src/shared-navigator.ts +3 -36
- package/node_modules/pi-better-plan/README.md +0 -1
- package/node_modules/pi-better-plan/package.json +1 -1
- package/node_modules/pi-better-plan/src/index.ts +6 -142
- package/node_modules/pi-better-plan/src/plan-render.ts +1 -4
- package/node_modules/pi-better-subagents/package.json +1 -1
- package/node_modules/pi-better-subagents/shared-navigator.ts +3 -36
- package/package.json +4 -4
|
@@ -97,7 +97,6 @@ type NavigatorState = {
|
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
const GLOBAL_KEY = Symbol.for("pi-better-harness.navigator.state");
|
|
100
|
-
const PLAN_NAVIGATION_KEY = Symbol.for("pi-better-harness.plan-navigation.state");
|
|
101
100
|
const FACTORY_MARK = "__piBetterHarnessNavigatorFactory";
|
|
102
101
|
const FACTORY_REFRESH = "__piBetterHarnessNavigatorRefresh";
|
|
103
102
|
|
|
@@ -121,23 +120,6 @@ function state(): NavigatorState {
|
|
|
121
120
|
return g[GLOBAL_KEY]!;
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
type PlanNavigationState = {
|
|
125
|
-
visible: boolean;
|
|
126
|
-
progressLabel?: string;
|
|
127
|
-
releaseWorkFocus?: () => void;
|
|
128
|
-
refreshNavigationHint?: () => void;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
function planNavigationState(): PlanNavigationState {
|
|
132
|
-
const global = globalThis as typeof globalThis & { [PLAN_NAVIGATION_KEY]?: PlanNavigationState };
|
|
133
|
-
if (!global[PLAN_NAVIGATION_KEY]) global[PLAN_NAVIGATION_KEY] = { visible: false };
|
|
134
|
-
return global[PLAN_NAVIGATION_KEY]!;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function refreshPlanNavigationHint(): void {
|
|
138
|
-
refreshBackgroundWorkNavigator();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
123
|
export function registerBackgroundWorkProvider(provider: BackgroundWorkProvider): () => void {
|
|
142
124
|
const s = state();
|
|
143
125
|
const previousUnsub = s.unsubscribers.get(provider.id);
|
|
@@ -179,12 +161,7 @@ export function isNavigatorUiAvailable(ctx: ExtensionContext | undefined): boole
|
|
|
179
161
|
}
|
|
180
162
|
|
|
181
163
|
export function navigatorFooterHint(count: number): string | null {
|
|
182
|
-
|
|
183
|
-
const navigation = planNavigationState();
|
|
184
|
-
const plan = navigation.visible
|
|
185
|
-
? `→ plan${navigation.progressLabel ? ` · ${navigation.progressLabel}` : ""}`
|
|
186
|
-
: null;
|
|
187
|
-
return [work, plan].filter((hint): hint is string => hint !== null).join(" ") || null;
|
|
164
|
+
return count > 0 ? `← work · ${count}` : null;
|
|
188
165
|
}
|
|
189
166
|
|
|
190
167
|
export function applyNavigatorFooter(ui: { setStatus(key: string, value: string | undefined): void }, count: number): string | null {
|
|
@@ -208,10 +185,6 @@ export function ensureBackgroundWorkNavigator(ctx: ExtensionContext, deps: HostD
|
|
|
208
185
|
s.mainListRequestRender = undefined;
|
|
209
186
|
s.mainListDeadlineScheduler?.dispose();
|
|
210
187
|
s.mainListDeadlineScheduler = createRenderScheduler(() => refreshMainListWidget());
|
|
211
|
-
const planNavigation = planNavigationState();
|
|
212
|
-
planNavigation.releaseWorkFocus = unfocusMainList;
|
|
213
|
-
planNavigation.refreshNavigationHint = refreshPlanNavigationHint;
|
|
214
|
-
try { (ctx.ui as any).setStatus?.("pi-better-plan-nav", undefined); } catch { /* ignore */ }
|
|
215
188
|
installNavigatorEditor(ctx.ui as any, deps);
|
|
216
189
|
s.lastHint = undefined;
|
|
217
190
|
refreshBackgroundWorkNavigator(ctx);
|
|
@@ -240,11 +213,6 @@ export function disposeBackgroundWorkNavigator(ctx?: ExtensionContext): void {
|
|
|
240
213
|
s.mainListRequestRender = undefined;
|
|
241
214
|
s.editorComponent = undefined;
|
|
242
215
|
s.detailOverlayRows = undefined;
|
|
243
|
-
const planNavigation = planNavigationState();
|
|
244
|
-
planNavigation.releaseWorkFocus = undefined;
|
|
245
|
-
if (planNavigation.refreshNavigationHint === refreshPlanNavigationHint) {
|
|
246
|
-
planNavigation.refreshNavigationHint = undefined;
|
|
247
|
-
}
|
|
248
216
|
s.mainListSelectedId = undefined;
|
|
249
217
|
s.mainListFocused = false;
|
|
250
218
|
}
|
|
@@ -492,10 +460,9 @@ function buildMainListLines(
|
|
|
492
460
|
}
|
|
493
461
|
|
|
494
462
|
function shortcutsLine(focused: boolean, fg: (color: string, value: string) => string): string {
|
|
495
|
-
const plan = planNavigationState().visible ? " · → plan" : "";
|
|
496
463
|
const keys = focused
|
|
497
|
-
?
|
|
498
|
-
:
|
|
464
|
+
? "↑↓ switch · Enter detail · x stop · Esc unfocus"
|
|
465
|
+
: "← work navigator";
|
|
499
466
|
return dim(keys, fg);
|
|
500
467
|
}
|
|
501
468
|
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
- Gives models `update_plan` and `get_plan` tools for atomic, explicit progress updates.
|
|
8
8
|
- Shows the complete checklist of completed, active, pending, and blocked steps above the editor.
|
|
9
|
-
- Uses `→` from an empty editor to focus the plan; normal cursor movement is preserved when the editor contains text.
|
|
10
9
|
- Persists plan state and display preferences on the active Pi session branch.
|
|
11
10
|
- Opens the complete plan with `/plan`.
|
|
12
11
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -36,74 +36,32 @@ const UpdatePlanSchema = Type.Object({
|
|
|
36
36
|
plan: Type.Array(PlanStepSchema, { minItems: 1, maxItems: 50 }),
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
const
|
|
40
|
-
const PLAN_EDITOR_FACTORY_MARK = "__piBetterPlanFactory";
|
|
41
|
-
const PLAN_EDITOR_FACTORY_REFRESH = "__piBetterPlanRefresh";
|
|
42
|
-
const PLAN_NAVIGATION_KEY = Symbol.for("pi-better-harness.plan-navigation.state");
|
|
43
|
-
|
|
44
|
-
interface SharedPlanNavigationState {
|
|
45
|
-
visible: boolean;
|
|
46
|
-
progressLabel?: string;
|
|
47
|
-
releaseWorkFocus?: () => void;
|
|
48
|
-
refreshNavigationHint?: () => void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function sharedPlanNavigationState(): SharedPlanNavigationState {
|
|
52
|
-
const global = globalThis as typeof globalThis & { [PLAN_NAVIGATION_KEY]?: SharedPlanNavigationState };
|
|
53
|
-
if (!global[PLAN_NAVIGATION_KEY]) global[PLAN_NAVIGATION_KEY] = { visible: false };
|
|
54
|
-
return global[PLAN_NAVIGATION_KEY]!;
|
|
55
|
-
}
|
|
39
|
+
const LEGACY_PLAN_NAV_STATUS_KEY = "pi-better-plan-nav";
|
|
56
40
|
|
|
57
41
|
export default function planExtension(pi: ExtensionAPI): void {
|
|
58
42
|
let currentPlan: PlanSnapshot | null = null;
|
|
59
43
|
let displayMode: PlanDisplayMode = "auto";
|
|
60
|
-
let selectedIndex = 0;
|
|
61
|
-
let focused = false;
|
|
62
44
|
let refreshWidget: ((force?: boolean) => void) | undefined;
|
|
63
|
-
let currentCtx: ExtensionContext | undefined;
|
|
64
45
|
|
|
65
46
|
const refresh = (force = false): void => refreshWidget?.(force);
|
|
66
47
|
|
|
67
|
-
const updateNavigationHint = (ctx = currentCtx): void => {
|
|
68
|
-
if (!ctx?.hasUI) return;
|
|
69
|
-
const progress = currentPlan ? planProgress(currentPlan) : null;
|
|
70
|
-
const visible = currentPlan !== null && displayMode !== "hidden";
|
|
71
|
-
const navigation = sharedPlanNavigationState();
|
|
72
|
-
navigation.visible = visible;
|
|
73
|
-
const suffix = progress?.blocked ? "blocked" : progress ? `${progress.completed}/${progress.total}` : "";
|
|
74
|
-
if (suffix) navigation.progressLabel = suffix;
|
|
75
|
-
else delete navigation.progressLabel;
|
|
76
|
-
if (navigation.refreshNavigationHint) {
|
|
77
|
-
ctx.ui.setStatus(PLAN_NAV_STATUS_KEY, undefined);
|
|
78
|
-
navigation.refreshNavigationHint();
|
|
79
|
-
} else {
|
|
80
|
-
ctx.ui.setStatus(PLAN_NAV_STATUS_KEY, visible ? `→ plan · ${suffix}` : undefined);
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
48
|
const restore = (ctx: ExtensionContext): void => {
|
|
85
49
|
const state = reconstructPlanState(ctx.sessionManager.getBranch());
|
|
86
50
|
currentPlan = state.plan;
|
|
87
51
|
displayMode = state.displayMode;
|
|
88
|
-
|
|
89
|
-
focused = false;
|
|
90
|
-
updateNavigationHint(ctx);
|
|
52
|
+
if (ctx.hasUI) ctx.ui.setStatus(LEGACY_PLAN_NAV_STATUS_KEY, undefined);
|
|
91
53
|
refresh(true);
|
|
92
54
|
};
|
|
93
55
|
|
|
94
56
|
const persistPlan = (plan: PlanSnapshot): void => {
|
|
95
57
|
currentPlan = plan;
|
|
96
|
-
selectedIndex = preferredIndex(plan);
|
|
97
|
-
focused = false;
|
|
98
58
|
pi.appendEntry(EXTENSION_NAME, planSetEntry(plan));
|
|
99
|
-
updateNavigationHint();
|
|
100
59
|
refresh(true);
|
|
101
60
|
};
|
|
102
61
|
|
|
103
62
|
const setDisplayMode = (mode: PlanDisplayMode): void => {
|
|
104
63
|
displayMode = mode;
|
|
105
64
|
pi.appendEntry(EXTENSION_NAME, planDisplayEntry(mode));
|
|
106
|
-
updateNavigationHint();
|
|
107
65
|
refresh(true);
|
|
108
66
|
};
|
|
109
67
|
|
|
@@ -117,7 +75,7 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
117
75
|
return {
|
|
118
76
|
render(width: number): string[] {
|
|
119
77
|
if (!currentPlan || displayMode === "hidden") return [];
|
|
120
|
-
return renderCompactPlan(currentPlan, width, theme as never
|
|
78
|
+
return renderCompactPlan(currentPlan, width, theme as never);
|
|
121
79
|
},
|
|
122
80
|
invalidate() {},
|
|
123
81
|
dispose() {
|
|
@@ -139,8 +97,6 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
139
97
|
return;
|
|
140
98
|
}
|
|
141
99
|
const snapshot = currentPlan;
|
|
142
|
-
focused = false;
|
|
143
|
-
refresh();
|
|
144
100
|
await ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
|
|
145
101
|
const component = createFullPlanComponent(snapshot, theme as never, () => done());
|
|
146
102
|
return {
|
|
@@ -154,80 +110,6 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
154
110
|
});
|
|
155
111
|
};
|
|
156
112
|
|
|
157
|
-
const handlePlanNavigation = (data: string, ctx: ExtensionContext): boolean => {
|
|
158
|
-
if (!currentPlan || displayMode === "hidden") return false;
|
|
159
|
-
if (!focused) {
|
|
160
|
-
if (!matchesKey(data, Key.right)) return false;
|
|
161
|
-
sharedPlanNavigationState().releaseWorkFocus?.();
|
|
162
|
-
focused = true;
|
|
163
|
-
selectedIndex = preferredIndex(currentPlan);
|
|
164
|
-
refresh();
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
if (matchesKey(data, Key.up)) {
|
|
168
|
-
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
169
|
-
refresh();
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
if (matchesKey(data, Key.down)) {
|
|
173
|
-
selectedIndex = Math.min(currentPlan.steps.length - 1, selectedIndex + 1);
|
|
174
|
-
refresh();
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
if (matchesKey(data, Key.enter) || matchesKey(data, Key.right)) {
|
|
178
|
-
void showFullPlan(ctx);
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
if (matchesKey(data, Key.left) || matchesKey(data, Key.escape)) {
|
|
182
|
-
focused = false;
|
|
183
|
-
refresh();
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
focused = false;
|
|
187
|
-
refresh();
|
|
188
|
-
return false;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const installEditorNavigation = (ctx: ExtensionContext): void => {
|
|
192
|
-
if (ctx.mode !== "tui") return;
|
|
193
|
-
const ui = ctx.ui as typeof ctx.ui & {
|
|
194
|
-
getEditorComponent?(): ((tui: unknown, theme: unknown, keybindings: unknown) => unknown) | undefined;
|
|
195
|
-
setEditorComponent(factory: (tui: unknown, theme: unknown, keybindings: unknown) => unknown): void;
|
|
196
|
-
};
|
|
197
|
-
const previous = ui.getEditorComponent?.() as
|
|
198
|
-
| (((tui: unknown, theme: unknown, keybindings: unknown) => unknown) & Record<string, unknown>)
|
|
199
|
-
| undefined;
|
|
200
|
-
const handlers = { handle: (data: string) => handlePlanNavigation(data, ctx) };
|
|
201
|
-
if (previous?.[PLAN_EDITOR_FACTORY_MARK] === true) {
|
|
202
|
-
const replaceHandlers = previous[PLAN_EDITOR_FACTORY_REFRESH];
|
|
203
|
-
if (typeof replaceHandlers === "function") replaceHandlers(handlers);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
let currentHandlers = handlers;
|
|
207
|
-
const factory = ((tui: unknown, theme: unknown, keybindings: unknown) => {
|
|
208
|
-
const inner = (previous
|
|
209
|
-
? previous(tui, theme, keybindings)
|
|
210
|
-
: new CustomEditor(tui as never, theme as never, keybindings as never)) as {
|
|
211
|
-
getText?(): string;
|
|
212
|
-
handleInput(data: string): void;
|
|
213
|
-
};
|
|
214
|
-
return new Proxy(inner, {
|
|
215
|
-
get(target, property) {
|
|
216
|
-
if (property === "handleInput") {
|
|
217
|
-
return (data: string) => {
|
|
218
|
-
if (target.getText?.() === "" && currentHandlers.handle(data)) return;
|
|
219
|
-
target.handleInput(data);
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
const value = Reflect.get(target, property);
|
|
223
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
}) as ((tui: unknown, theme: unknown, keybindings: unknown) => unknown) & Record<string, unknown>;
|
|
227
|
-
factory[PLAN_EDITOR_FACTORY_MARK] = true;
|
|
228
|
-
factory[PLAN_EDITOR_FACTORY_REFRESH] = (next: typeof handlers) => { currentHandlers = next; };
|
|
229
|
-
ui.setEditorComponent(factory);
|
|
230
|
-
};
|
|
231
113
|
|
|
232
114
|
pi.registerTool({
|
|
233
115
|
name: "update_plan",
|
|
@@ -290,9 +172,7 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
290
172
|
if (!input) return showFullPlan(ctx);
|
|
291
173
|
if (input === "clear") {
|
|
292
174
|
currentPlan = null;
|
|
293
|
-
focused = false;
|
|
294
175
|
pi.appendEntry(EXTENSION_NAME, planClearEntry());
|
|
295
|
-
updateNavigationHint(ctx);
|
|
296
176
|
refresh(true);
|
|
297
177
|
ctx.ui.notify("Plan cleared.", "info");
|
|
298
178
|
return;
|
|
@@ -316,13 +196,10 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
316
196
|
});
|
|
317
197
|
|
|
318
198
|
pi.on("session_start", async (_event, ctx) => {
|
|
319
|
-
currentCtx = ctx;
|
|
320
199
|
installWidget(ctx);
|
|
321
|
-
installEditorNavigation(ctx);
|
|
322
200
|
restore(ctx);
|
|
323
201
|
});
|
|
324
202
|
pi.on("session_tree", async (_event, ctx) => {
|
|
325
|
-
currentCtx = ctx;
|
|
326
203
|
restore(ctx);
|
|
327
204
|
});
|
|
328
205
|
pi.on("before_agent_start", async (event) => {
|
|
@@ -333,26 +210,13 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
333
210
|
});
|
|
334
211
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
335
212
|
refreshWidget = undefined;
|
|
336
|
-
currentCtx = undefined;
|
|
337
|
-
const navigation = sharedPlanNavigationState();
|
|
338
|
-
navigation.visible = false;
|
|
339
|
-
delete navigation.progressLabel;
|
|
340
|
-
navigation.refreshNavigationHint?.();
|
|
341
213
|
try {
|
|
342
|
-
ctx.ui.setStatus(
|
|
214
|
+
ctx.ui.setStatus(LEGACY_PLAN_NAV_STATUS_KEY, undefined);
|
|
343
215
|
ctx.ui.setWidget(EXTENSION_NAME, undefined);
|
|
344
216
|
} catch { /* best-effort cleanup */ }
|
|
345
217
|
});
|
|
346
218
|
}
|
|
347
219
|
|
|
348
|
-
function preferredIndex(plan: PlanSnapshot | null): number {
|
|
349
|
-
if (!plan) return 0;
|
|
350
|
-
const progress = planProgress(plan);
|
|
351
|
-
if (progress.activeIndex !== null) return progress.activeIndex;
|
|
352
|
-
const next = plan.steps.findIndex((item) => item.status !== "completed");
|
|
353
|
-
return next >= 0 ? next : Math.max(0, plan.steps.length - 1);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
220
|
function formatProgress(plan: PlanSnapshot): string {
|
|
357
221
|
const progress = planProgress(plan);
|
|
358
222
|
const current = progress.activeIndex === null ? null : plan.steps[progress.activeIndex];
|
|
@@ -12,10 +12,8 @@ export function renderCompactPlan(
|
|
|
12
12
|
plan: PlanSnapshot,
|
|
13
13
|
width: number,
|
|
14
14
|
theme: PlanRenderTheme,
|
|
15
|
-
options: { focused?: boolean; selectedIndex?: number } = {},
|
|
16
15
|
): string[] {
|
|
17
16
|
const progress = planProgress(plan);
|
|
18
|
-
const selected = clampIndex(options.selectedIndex ?? progress.activeIndex ?? firstIncompleteIndex(plan), plan.steps.length);
|
|
19
17
|
const stateLabel = progress.blocked > 0
|
|
20
18
|
? `${progress.blocked} blocked`
|
|
21
19
|
: progress.state === "complete"
|
|
@@ -28,8 +26,7 @@ export function renderCompactPlan(
|
|
|
28
26
|
|
|
29
27
|
for (let index = 0; index < plan.steps.length; index += 1) {
|
|
30
28
|
const item = plan.steps[index]!;
|
|
31
|
-
|
|
32
|
-
lines.push(`${selectedPrefix}${stepGlyph(item, theme)} ${index + 1} ${stepText(item, theme)}`);
|
|
29
|
+
lines.push(` ${stepGlyph(item, theme)} ${index + 1} ${stepText(item, theme)}`);
|
|
33
30
|
}
|
|
34
31
|
lines.push("");
|
|
35
32
|
return lines.map((line) => truncateToWidth(line, Math.max(1, width)));
|
|
@@ -97,7 +97,6 @@ type NavigatorState = {
|
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
const GLOBAL_KEY = Symbol.for("pi-better-harness.navigator.state");
|
|
100
|
-
const PLAN_NAVIGATION_KEY = Symbol.for("pi-better-harness.plan-navigation.state");
|
|
101
100
|
const FACTORY_MARK = "__piBetterHarnessNavigatorFactory";
|
|
102
101
|
const FACTORY_REFRESH = "__piBetterHarnessNavigatorRefresh";
|
|
103
102
|
|
|
@@ -121,23 +120,6 @@ function state(): NavigatorState {
|
|
|
121
120
|
return g[GLOBAL_KEY]!;
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
type PlanNavigationState = {
|
|
125
|
-
visible: boolean;
|
|
126
|
-
progressLabel?: string;
|
|
127
|
-
releaseWorkFocus?: () => void;
|
|
128
|
-
refreshNavigationHint?: () => void;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
function planNavigationState(): PlanNavigationState {
|
|
132
|
-
const global = globalThis as typeof globalThis & { [PLAN_NAVIGATION_KEY]?: PlanNavigationState };
|
|
133
|
-
if (!global[PLAN_NAVIGATION_KEY]) global[PLAN_NAVIGATION_KEY] = { visible: false };
|
|
134
|
-
return global[PLAN_NAVIGATION_KEY]!;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function refreshPlanNavigationHint(): void {
|
|
138
|
-
refreshBackgroundWorkNavigator();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
123
|
export function registerBackgroundWorkProvider(provider: BackgroundWorkProvider): () => void {
|
|
142
124
|
const s = state();
|
|
143
125
|
const previousUnsub = s.unsubscribers.get(provider.id);
|
|
@@ -179,12 +161,7 @@ export function isNavigatorUiAvailable(ctx: ExtensionContext | undefined): boole
|
|
|
179
161
|
}
|
|
180
162
|
|
|
181
163
|
export function navigatorFooterHint(count: number): string | null {
|
|
182
|
-
|
|
183
|
-
const navigation = planNavigationState();
|
|
184
|
-
const plan = navigation.visible
|
|
185
|
-
? `→ plan${navigation.progressLabel ? ` · ${navigation.progressLabel}` : ""}`
|
|
186
|
-
: null;
|
|
187
|
-
return [work, plan].filter((hint): hint is string => hint !== null).join(" ") || null;
|
|
164
|
+
return count > 0 ? `← work · ${count}` : null;
|
|
188
165
|
}
|
|
189
166
|
|
|
190
167
|
export function applyNavigatorFooter(ui: { setStatus(key: string, value: string | undefined): void }, count: number): string | null {
|
|
@@ -208,10 +185,6 @@ export function ensureBackgroundWorkNavigator(ctx: ExtensionContext, deps: HostD
|
|
|
208
185
|
s.mainListRequestRender = undefined;
|
|
209
186
|
s.mainListDeadlineScheduler?.dispose();
|
|
210
187
|
s.mainListDeadlineScheduler = createRenderScheduler(() => refreshMainListWidget());
|
|
211
|
-
const planNavigation = planNavigationState();
|
|
212
|
-
planNavigation.releaseWorkFocus = unfocusMainList;
|
|
213
|
-
planNavigation.refreshNavigationHint = refreshPlanNavigationHint;
|
|
214
|
-
try { (ctx.ui as any).setStatus?.("pi-better-plan-nav", undefined); } catch { /* ignore */ }
|
|
215
188
|
installNavigatorEditor(ctx.ui as any, deps);
|
|
216
189
|
s.lastHint = undefined;
|
|
217
190
|
refreshBackgroundWorkNavigator(ctx);
|
|
@@ -240,11 +213,6 @@ export function disposeBackgroundWorkNavigator(ctx?: ExtensionContext): void {
|
|
|
240
213
|
s.mainListRequestRender = undefined;
|
|
241
214
|
s.editorComponent = undefined;
|
|
242
215
|
s.detailOverlayRows = undefined;
|
|
243
|
-
const planNavigation = planNavigationState();
|
|
244
|
-
planNavigation.releaseWorkFocus = undefined;
|
|
245
|
-
if (planNavigation.refreshNavigationHint === refreshPlanNavigationHint) {
|
|
246
|
-
planNavigation.refreshNavigationHint = undefined;
|
|
247
|
-
}
|
|
248
216
|
s.mainListSelectedId = undefined;
|
|
249
217
|
s.mainListFocused = false;
|
|
250
218
|
}
|
|
@@ -492,10 +460,9 @@ function buildMainListLines(
|
|
|
492
460
|
}
|
|
493
461
|
|
|
494
462
|
function shortcutsLine(focused: boolean, fg: (color: string, value: string) => string): string {
|
|
495
|
-
const plan = planNavigationState().visible ? " · → plan" : "";
|
|
496
463
|
const keys = focused
|
|
497
|
-
?
|
|
498
|
-
:
|
|
464
|
+
? "↑↓ switch · Enter detail · x stop · Esc unfocus"
|
|
465
|
+
: "← work navigator";
|
|
499
466
|
return dim(keys, fg);
|
|
500
467
|
}
|
|
501
468
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-harness",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Pi extension bundle for a write sandbox, subagents, background tasks, SSH, goals, and structured plans.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"test": "node --test test/*.test.mjs"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"pi-better-background-tasks": "0.2.
|
|
53
|
+
"pi-better-background-tasks": "0.2.11",
|
|
54
54
|
"pi-better-goal": "0.2.1",
|
|
55
|
-
"pi-better-plan": "0.1.
|
|
55
|
+
"pi-better-plan": "0.1.3",
|
|
56
56
|
"pi-better-sandbox": "0.3.0",
|
|
57
57
|
"pi-better-ssh": "0.1.1",
|
|
58
|
-
"pi-better-subagents": "0.1.
|
|
58
|
+
"pi-better-subagents": "0.1.26"
|
|
59
59
|
},
|
|
60
60
|
"bundledDependencies": [
|
|
61
61
|
"pi-better-background-tasks",
|