pi-better-harness 0.3.4 → 0.3.6

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-background-tasks",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Pi extension for durable background shell tasks, watchers, logs, and status inspection.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
- const work = count > 0 ? `← work · ${count}` : null;
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
- ? `↑↓ switch · Enter detail · x stop${plan} · Esc unfocus`
498
- : `← work navigator${plan}`;
464
+ ? "↑↓ switch · Enter detail · x stop · Esc unfocus"
465
+ : "← work navigator";
499
466
  return dim(keys, fg);
500
467
  }
501
468
 
@@ -6,8 +6,8 @@
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.
10
+ - Keeps a completed plan visible for 30 seconds, then clears it automatically.
11
11
  - Opens the complete plan with `/plan`.
12
12
 
13
13
  Plan progress is checklist progress, not an estimate of effort. The extension never infers completion from prose or successful tool calls.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-plan",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Structured execution plans with persistent progress for Pi.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -70,4 +70,4 @@
70
70
  "engines": {
71
71
  "node": ">=22.0.0"
72
72
  }
73
- }
73
+ }
@@ -1,9 +1,10 @@
1
1
  import { StringEnum } from "@earendil-works/pi-ai";
2
- import { CustomEditor, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
3
- import { Key, matchesKey, Text } from "@earendil-works/pi-tui";
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 {
7
+ completedPlanClearDelay,
7
8
  planClearEntry,
8
9
  planDisplayEntry,
9
10
  planProgress,
@@ -36,74 +37,64 @@ const UpdatePlanSchema = Type.Object({
36
37
  plan: Type.Array(PlanStepSchema, { minItems: 1, maxItems: 50 }),
37
38
  });
38
39
 
39
- const PLAN_NAV_STATUS_KEY = "pi-better-plan-nav";
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
- }
40
+ const LEGACY_PLAN_NAV_STATUS_KEY = "pi-better-plan-nav";
56
41
 
57
42
  export default function planExtension(pi: ExtensionAPI): void {
58
43
  let currentPlan: PlanSnapshot | null = null;
59
44
  let displayMode: PlanDisplayMode = "auto";
60
- let selectedIndex = 0;
61
- let focused = false;
62
45
  let refreshWidget: ((force?: boolean) => void) | undefined;
63
- let currentCtx: ExtensionContext | undefined;
46
+ let completedPlanClearTimer: ReturnType<typeof setTimeout> | undefined;
64
47
 
65
48
  const refresh = (force = false): void => refreshWidget?.(force);
66
49
 
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
- }
50
+ const cancelCompletedPlanClear = (): void => {
51
+ if (completedPlanClearTimer) clearTimeout(completedPlanClearTimer);
52
+ completedPlanClearTimer = undefined;
53
+ };
54
+
55
+ const clearPlan = (): void => {
56
+ cancelCompletedPlanClear();
57
+ currentPlan = null;
58
+ pi.appendEntry(EXTENSION_NAME, planClearEntry());
59
+ refresh(true);
60
+ };
61
+
62
+ const scheduleCompletedPlanClear = (): void => {
63
+ cancelCompletedPlanClear();
64
+ if (!currentPlan) return;
65
+ const delay = completedPlanClearDelay(currentPlan);
66
+ if (delay === null) return;
67
+ const completedPlan = currentPlan;
68
+ const timer = setTimeout(() => {
69
+ if (completedPlanClearTimer !== timer) return;
70
+ completedPlanClearTimer = undefined;
71
+ if (currentPlan?.planId !== completedPlan.planId || currentPlan.revision !== completedPlan.revision) return;
72
+ clearPlan();
73
+ }, delay);
74
+ completedPlanClearTimer = timer;
75
+ timer.unref?.();
82
76
  };
83
77
 
84
78
  const restore = (ctx: ExtensionContext): void => {
79
+ cancelCompletedPlanClear();
85
80
  const state = reconstructPlanState(ctx.sessionManager.getBranch());
86
81
  currentPlan = state.plan;
87
82
  displayMode = state.displayMode;
88
- selectedIndex = preferredIndex(currentPlan);
89
- focused = false;
90
- updateNavigationHint(ctx);
83
+ if (ctx.hasUI) ctx.ui.setStatus(LEGACY_PLAN_NAV_STATUS_KEY, undefined);
91
84
  refresh(true);
85
+ scheduleCompletedPlanClear();
92
86
  };
93
87
 
94
88
  const persistPlan = (plan: PlanSnapshot): void => {
95
89
  currentPlan = plan;
96
- selectedIndex = preferredIndex(plan);
97
- focused = false;
98
90
  pi.appendEntry(EXTENSION_NAME, planSetEntry(plan));
99
- updateNavigationHint();
100
91
  refresh(true);
92
+ scheduleCompletedPlanClear();
101
93
  };
102
94
 
103
95
  const setDisplayMode = (mode: PlanDisplayMode): void => {
104
96
  displayMode = mode;
105
97
  pi.appendEntry(EXTENSION_NAME, planDisplayEntry(mode));
106
- updateNavigationHint();
107
98
  refresh(true);
108
99
  };
109
100
 
@@ -117,7 +108,7 @@ export default function planExtension(pi: ExtensionAPI): void {
117
108
  return {
118
109
  render(width: number): string[] {
119
110
  if (!currentPlan || displayMode === "hidden") return [];
120
- return renderCompactPlan(currentPlan, width, theme as never, { focused, selectedIndex });
111
+ return renderCompactPlan(currentPlan, width, theme as never);
121
112
  },
122
113
  invalidate() {},
123
114
  dispose() {
@@ -139,8 +130,6 @@ export default function planExtension(pi: ExtensionAPI): void {
139
130
  return;
140
131
  }
141
132
  const snapshot = currentPlan;
142
- focused = false;
143
- refresh();
144
133
  await ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
145
134
  const component = createFullPlanComponent(snapshot, theme as never, () => done());
146
135
  return {
@@ -154,80 +143,6 @@ export default function planExtension(pi: ExtensionAPI): void {
154
143
  });
155
144
  };
156
145
 
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
146
 
232
147
  pi.registerTool({
233
148
  name: "update_plan",
@@ -289,11 +204,7 @@ export default function planExtension(pi: ExtensionAPI): void {
289
204
  const input = args.trim().toLowerCase();
290
205
  if (!input) return showFullPlan(ctx);
291
206
  if (input === "clear") {
292
- currentPlan = null;
293
- focused = false;
294
- pi.appendEntry(EXTENSION_NAME, planClearEntry());
295
- updateNavigationHint(ctx);
296
- refresh(true);
207
+ clearPlan();
297
208
  ctx.ui.notify("Plan cleared.", "info");
298
209
  return;
299
210
  }
@@ -316,13 +227,10 @@ export default function planExtension(pi: ExtensionAPI): void {
316
227
  });
317
228
 
318
229
  pi.on("session_start", async (_event, ctx) => {
319
- currentCtx = ctx;
320
230
  installWidget(ctx);
321
- installEditorNavigation(ctx);
322
231
  restore(ctx);
323
232
  });
324
233
  pi.on("session_tree", async (_event, ctx) => {
325
- currentCtx = ctx;
326
234
  restore(ctx);
327
235
  });
328
236
  pi.on("before_agent_start", async (event) => {
@@ -332,27 +240,15 @@ export default function planExtension(pi: ExtensionAPI): void {
332
240
  };
333
241
  });
334
242
  pi.on("session_shutdown", async (_event, ctx) => {
243
+ cancelCompletedPlanClear();
335
244
  refreshWidget = undefined;
336
- currentCtx = undefined;
337
- const navigation = sharedPlanNavigationState();
338
- navigation.visible = false;
339
- delete navigation.progressLabel;
340
- navigation.refreshNavigationHint?.();
341
245
  try {
342
- ctx.ui.setStatus(PLAN_NAV_STATUS_KEY, undefined);
246
+ ctx.ui.setStatus(LEGACY_PLAN_NAV_STATUS_KEY, undefined);
343
247
  ctx.ui.setWidget(EXTENSION_NAME, undefined);
344
248
  } catch { /* best-effort cleanup */ }
345
249
  });
346
250
  }
347
251
 
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
252
  function formatProgress(plan: PlanSnapshot): string {
357
253
  const progress = planProgress(plan);
358
254
  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
- const selectedPrefix = options.focused && index === selected ? theme.fg("accent", "› ") : " ";
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)));
@@ -4,6 +4,7 @@ import { EXTENSION_NAME } from "./types.js";
4
4
  const MAX_STEPS = 50;
5
5
  const MAX_STEP_CHARS = 500;
6
6
  const MAX_EXPLANATION_CHARS = 2_000;
7
+ export const COMPLETED_PLAN_RETENTION_MS = 30_000;
7
8
 
8
9
  interface SessionEntryLike {
9
10
  type: string;
@@ -54,6 +55,7 @@ export function replacePlan(
54
55
  steps: readonly PlanStepInput[],
55
56
  explanation?: string,
56
57
  now = nowSeconds(),
58
+ completedAtMs = Date.now(),
57
59
  ): PlanSnapshot {
58
60
  const error = validatePlanInput(steps, explanation);
59
61
  if (error) throw new Error(error);
@@ -68,6 +70,8 @@ export function replacePlan(
68
70
  status: item.status,
69
71
  };
70
72
  });
73
+ const isComplete = normalizedSteps.every((item) => item.status === "completed");
74
+ const currentIsComplete = current?.steps.every((item) => item.status === "completed") === true;
71
75
 
72
76
  return {
73
77
  version: 1,
@@ -77,9 +81,18 @@ export function replacePlan(
77
81
  steps: normalizedSteps,
78
82
  createdAt: current?.createdAt ?? now,
79
83
  updatedAt: now,
84
+ ...(isComplete
85
+ ? { completedAtMs: currentIsComplete ? current.completedAtMs ?? current.updatedAt * 1_000 : completedAtMs }
86
+ : {}),
80
87
  };
81
88
  }
82
89
 
90
+ export function completedPlanClearDelay(plan: PlanSnapshot, now = Date.now()): number | null {
91
+ if (planProgress(plan).state !== "complete") return null;
92
+ const completedAtMs = plan.completedAtMs ?? plan.updatedAt * 1_000;
93
+ return Math.max(0, completedAtMs + COMPLETED_PLAN_RETENTION_MS - now);
94
+ }
95
+
83
96
  export function planProgress(plan: PlanSnapshot): PlanProgress {
84
97
  const completed = plan.steps.filter((item) => item.status === "completed").length;
85
98
  const pending = plan.steps.filter((item) => item.status === "pending").length;
@@ -149,6 +162,7 @@ function isPlanSnapshot(value: unknown): value is PlanSnapshot {
149
162
  typeof candidate.revision === "number" &&
150
163
  Array.isArray(candidate.steps) &&
151
164
  typeof candidate.createdAt === "number" &&
152
- typeof candidate.updatedAt === "number"
165
+ typeof candidate.updatedAt === "number" &&
166
+ (candidate.completedAtMs === undefined || typeof candidate.completedAtMs === "number")
153
167
  );
154
168
  }
@@ -20,6 +20,7 @@ export interface PlanSnapshot {
20
20
  steps: PlanStep[];
21
21
  createdAt: number;
22
22
  updatedAt: number;
23
+ completedAtMs?: number;
23
24
  }
24
25
 
25
26
  export interface PlanProgress {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-subagents",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Pi extension for detached, sandboxed subagent runs that keep the foreground session free.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
- const work = count > 0 ? `← work · ${count}` : null;
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
- ? `↑↓ switch · Enter detail · x stop${plan} · Esc unfocus`
498
- : `← work navigator${plan}`;
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.4",
3
+ "version": "0.3.6",
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.10",
53
+ "pi-better-background-tasks": "0.2.11",
54
54
  "pi-better-goal": "0.2.1",
55
- "pi-better-plan": "0.1.2",
55
+ "pi-better-plan": "0.1.4",
56
56
  "pi-better-sandbox": "0.3.0",
57
57
  "pi-better-ssh": "0.1.1",
58
- "pi-better-subagents": "0.1.25"
58
+ "pi-better-subagents": "0.1.26"
59
59
  },
60
60
  "bundledDependencies": [
61
61
  "pi-better-background-tasks",