pi-better-harness 0.1.18 → 0.1.19

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-goal",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Pi extension for goal tracking with background-aware continuation.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -164,7 +164,7 @@ export default function (pi: ExtensionAPI): void {
164
164
  let continuationQueuedFor: string | null = null;
165
165
  let idleContinuationTimer: ReturnType<typeof setTimeout> | undefined;
166
166
  let idleContinuationSignature = "";
167
- let refreshGoalWidget: (() => void) | undefined;
167
+ let refreshGoalWidget: ((force?: boolean) => void) | undefined;
168
168
  let lastAgentEvidence: ContinuationEvidence | null = null;
169
169
 
170
170
  const getGoal = (ctx: ExtensionContext): GoalSnapshot | null => currentGoalSnapshot(ctx);
@@ -180,21 +180,53 @@ export default function (pi: ExtensionAPI): void {
180
180
  appendContinuationState(createContinuationState(goal.goalId));
181
181
  };
182
182
 
183
+ const isForegroundBusy = (ctx: ExtensionContext): boolean =>
184
+ foregroundRunning || !ctx.isIdle();
185
+
186
+ /**
187
+ * Chat `notify` appends lines above the Working/bash status region. While the
188
+ * agent is streaming, that height change desyncs Pi main-screen differential
189
+ * rendering and stacks "Working..." / "Elapsed Xs" into scrollback. Keep
190
+ * mid-stream feedback out of the transcript; use the footer status instead.
191
+ */
192
+ const notifyGoal = (
193
+ ctx: ExtensionContext,
194
+ message: string,
195
+ type: "info" | "warning" | "error" = "info",
196
+ ): void => {
197
+ if (!ctx.hasUI) {
198
+ return;
199
+ }
200
+ if (isForegroundBusy(ctx) && type !== "error") {
201
+ try {
202
+ ctx.ui.setStatus(EXTENSION_NAME, message);
203
+ } catch {
204
+ // Footer status is best-effort only.
205
+ }
206
+ return;
207
+ }
208
+ ctx.ui.notify(message, type);
209
+ };
210
+
183
211
  const setGoal = (goal: GoalSnapshot, ctx: ExtensionContext, source: GoalEntrySource): void => {
212
+ const previous = getGoal(ctx);
213
+ const wasVisible = previous !== null && isGoalClockVisible(previous);
184
214
  pi.appendEntry(EXTENSION_NAME, goalSetEntry(goal, source));
185
215
  continuationQueuedFor = null;
186
216
  backgroundDrainTracker = null;
187
217
  clearIdleContinuation();
188
- refreshGoalWidget?.();
218
+ // Force a full redraw when the dock height changes (absent ↔ visible clock).
219
+ refreshGoalWidget?.(!wasVisible || !isGoalClockVisible(goal));
189
220
  };
190
221
 
191
222
  const clearGoal = (ctx: ExtensionContext, source: GoalEntrySource): void => {
192
223
  const current = getGoal(ctx);
224
+ const wasVisible = current !== null && isGoalClockVisible(current);
193
225
  pi.appendEntry(EXTENSION_NAME, goalClearEntry(current?.goalId ?? null, source));
194
226
  continuationQueuedFor = null;
195
227
  backgroundDrainTracker = null;
196
228
  clearIdleContinuation();
197
- refreshGoalWidget?.();
229
+ refreshGoalWidget?.(wasVisible);
198
230
  };
199
231
 
200
232
  const queueGoalContinuation = (goal: GoalSnapshot): void => {
@@ -395,8 +427,24 @@ export default function (pi: ExtensionAPI): void {
395
427
  ctx.ui.setWidget(
396
428
  EXTENSION_NAME,
397
429
  (tui, theme) => {
398
- const scheduler = createRenderScheduler(() => tui.requestRender());
399
- const refresh = (): void => scheduler.request();
430
+ const requestRender = (force = false): void => {
431
+ try {
432
+ tui.requestRender(force);
433
+ } catch {
434
+ // UI paint is best-effort only.
435
+ }
436
+ };
437
+ const scheduler = createRenderScheduler(() => requestRender(false));
438
+ const refresh = (force = false): void => {
439
+ if (force) {
440
+ scheduler.cancel();
441
+ // Height transitions must reset main-screen differential state so
442
+ // Working/Elapsed lines keep rewriting in place instead of stacking.
443
+ requestRender(true);
444
+ return;
445
+ }
446
+ scheduler.request();
447
+ };
400
448
  refreshGoalWidget = refresh;
401
449
 
402
450
  return {
@@ -444,49 +492,53 @@ export default function (pi: ExtensionAPI): void {
444
492
 
445
493
  if (!trimmed) {
446
494
  const continuation = current ? currentContinuationState(ctx, current.goalId) : null;
495
+ // Inspection is always explicit user intent; show the full summary even while busy.
447
496
  ctx.ui.notify(formatGoal(current, continuation, observeGoalStall(current, continuation, { foregroundRunning })), "info");
448
497
  return;
449
498
  }
450
499
 
451
500
  if (trimmed === "pause") {
452
501
  if (!current || current.status !== "active") {
453
- ctx.ui.notify("Only active goals can be paused.", "warning");
502
+ notifyGoal(ctx, "Only active goals can be paused.", "warning");
454
503
  return;
455
504
  }
456
505
  setGoal(goalWithStatus(current, "paused"), ctx, "command");
457
- ctx.ui.notify("Goal paused.", "info");
506
+ notifyGoal(ctx, "Goal paused.");
458
507
  return;
459
508
  }
460
509
 
461
510
  if (trimmed === "resume") {
462
511
  if (!current || current.status !== "paused") {
463
- ctx.ui.notify("Only paused goals can be resumed.", "warning");
512
+ notifyGoal(ctx, "Only paused goals can be resumed.", "warning");
464
513
  return;
465
514
  }
466
515
  const goal = goalWithStatus(current, "active");
467
516
  setGoal(goal, ctx, "command");
468
517
  queueGoalContinuation(goal);
469
- ctx.ui.notify("Goal resumed.", "info");
518
+ notifyGoal(ctx, "Goal resumed.");
470
519
  return;
471
520
  }
472
521
 
473
522
  if (trimmed === "clear") {
474
523
  clearGoal(ctx, "command");
475
- ctx.ui.notify("Goal cleared.", "info");
524
+ notifyGoal(ctx, "Goal cleared.");
476
525
  return;
477
526
  }
478
527
 
479
528
  if (trimmed === "complete") {
480
529
  if (!current) {
481
- ctx.ui.notify("No goal is set.", "warning");
530
+ notifyGoal(ctx, "No goal is set.", "warning");
482
531
  return;
483
532
  }
484
533
  setGoal(goalWithStatus(current, "complete"), ctx, "command");
485
- ctx.ui.notify("Goal marked complete.", "info");
534
+ notifyGoal(ctx, "Goal marked complete.");
486
535
  return;
487
536
  }
488
537
 
489
- if (current && current.status !== "complete" && ctx.hasUI) {
538
+ // Confirm dialogs swap the editor and reflow the dock. While the agent is
539
+ // streaming that desyncs main-screen differential paints, so replace
540
+ // silently mid-turn and only confirm when idle.
541
+ if (current && current.status !== "complete" && ctx.hasUI && !isForegroundBusy(ctx)) {
490
542
  const replace = await ctx.ui.confirm(
491
543
  "Replace active goal?",
492
544
  `Current goal: ${current.objective}`,
@@ -498,9 +550,9 @@ export default function (pi: ExtensionAPI): void {
498
550
 
499
551
  try {
500
552
  const goal = startOrReplaceGoal(trimmed, null, ctx, "command");
501
- ctx.ui.notify(`Goal set: ${goal.objective}`, "info");
553
+ notifyGoal(ctx, `Goal set: ${goal.objective}`);
502
554
  } catch (error) {
503
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
555
+ notifyGoal(ctx, error instanceof Error ? error.message : String(error), "error");
504
556
  }
505
557
  },
506
558
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-harness",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Pi extension bundle for subagents, durable background tasks, and goal tracking.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "pi-better-background-tasks": "0.2.0",
47
- "pi-better-goal": "0.1.20",
47
+ "pi-better-goal": "0.1.21",
48
48
  "pi-better-subagents": "0.1.19"
49
49
  },
50
50
  "bundledDependencies": [