pi-long-task 0.3.6 → 0.3.7

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +61 -52
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-long-task",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "type": "module",
5
5
  "description": "Pi extension for breaking down and running long coding tasks safely.",
6
6
  "keywords": [
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
2
2
  import type { AssistantMessage } from "@earendil-works/pi-ai";
3
- import { truncateToWidth, type Component, type OverlayHandle, type TUI } from "@earendil-works/pi-tui";
3
+ import { truncateToWidth, type Component, type TUI } from "@earendil-works/pi-tui";
4
4
 
5
5
  import { runCoordinator, type CoordinatorProgressUpdate, type CoordinatorResult } from "./coordinator.ts";
6
6
  import { longTaskInputTransform } from "./input_router.ts";
@@ -76,6 +76,9 @@ function toolDetails(result: CoordinatorResult) {
76
76
  }
77
77
 
78
78
  const LONG_TASK_WIDGET_KEY = "pi-long-task-sidebar";
79
+ const SIDEBAR_WIDGET_RESERVED_INPUT_ROWS = 8;
80
+ const SIDEBAR_WIDGET_MIN_ROWS = 4;
81
+ const SIDEBAR_WIDGET_MAX_ROWS = 24;
79
82
 
80
83
  type UiContext = ExtensionContext;
81
84
 
@@ -86,10 +89,12 @@ export interface LongTaskSidebarController {
86
89
 
87
90
  class PiLongTaskSidebarComponent implements Component {
88
91
  private readonly theme: Theme;
92
+ private readonly maxRows: (() => number | undefined) | undefined;
89
93
  private update: CoordinatorProgressUpdate | undefined;
90
94
 
91
- constructor(theme: Theme) {
95
+ constructor(theme: Theme, maxRows?: () => number | undefined) {
92
96
  this.theme = theme;
97
+ this.maxRows = maxRows;
93
98
  }
94
99
 
95
100
  setUpdate(update: CoordinatorProgressUpdate): void {
@@ -98,7 +103,8 @@ class PiLongTaskSidebarComponent implements Component {
98
103
  }
99
104
 
100
105
  render(width: number): string[] {
101
- return renderSidebarOverlayLines(this.update, this.theme, width);
106
+ const lines = renderSidebarOverlayLines(this.update, this.theme, width);
107
+ return limitSidebarPanelLines(lines, this.theme, width, this.maxRows?.());
102
108
  }
103
109
 
104
110
  invalidate(): void {
@@ -112,48 +118,25 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
112
118
  }
113
119
 
114
120
  let latestUpdate: CoordinatorProgressUpdate | undefined;
115
- let overlayComponent: PiLongTaskSidebarComponent | undefined;
116
- let overlayTui: TUI | undefined;
117
- let overlayDone: ((result: undefined) => void) | undefined;
118
- let overlayHandle: OverlayHandle | undefined;
121
+ let sidebarComponent: PiLongTaskSidebarComponent | undefined;
122
+ let sidebarTui: TUI | undefined;
119
123
  let closed = false;
120
124
 
121
- ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, ["Pi Long Task: preparing sidebar..."], { placement: "aboveEditor" });
122
-
123
- if (supportsTuiOverlay(ctx)) {
124
- const overlayPromise = ctx.ui.custom<undefined>(
125
- (tui, theme, _keybindings, done) => {
126
- overlayTui = tui;
127
- overlayDone = done;
128
- overlayComponent = new PiLongTaskSidebarComponent(theme);
125
+ if (supportsTuiWidget(ctx)) {
126
+ ctx.ui.setWidget(
127
+ LONG_TASK_WIDGET_KEY,
128
+ (tui, theme) => {
129
+ sidebarTui = tui;
130
+ sidebarComponent = new PiLongTaskSidebarComponent(theme, () => sidebarWidgetLineLimit(tui));
129
131
  if (latestUpdate) {
130
- overlayComponent.setUpdate(latestUpdate);
131
- }
132
- if (closed) {
133
- done(undefined);
132
+ sidebarComponent.setUpdate(latestUpdate);
134
133
  }
135
- return overlayComponent;
136
- },
137
- {
138
- overlay: true,
139
- overlayOptions: {
140
- anchor: "right-center",
141
- width: "34%",
142
- minWidth: 36,
143
- maxHeight: "100%",
144
- margin: 0,
145
- nonCapturing: true,
146
- visible: (termWidth, termHeight) => termWidth >= 96 && termHeight >= 16,
147
- },
148
- onHandle: (handle) => {
149
- overlayHandle = handle;
150
- handle.unfocus();
151
- },
134
+ return sidebarComponent;
152
135
  },
136
+ { placement: "aboveEditor" },
153
137
  );
154
- void overlayPromise.catch(() => {
155
- // The widget fallback remains active if overlay registration is unavailable.
156
- });
138
+ } else {
139
+ ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, ["Pi Long Task: preparing sidebar..."], { placement: "aboveEditor" });
157
140
  }
158
141
 
159
142
  return {
@@ -162,9 +145,11 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
162
145
  return;
163
146
  }
164
147
  latestUpdate = update;
165
- overlayComponent?.setUpdate(update);
166
- overlayTui?.requestRender();
167
- ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, renderSidebarWidgetLines(update), { placement: "aboveEditor" });
148
+ sidebarComponent?.setUpdate(update);
149
+ sidebarTui?.requestRender();
150
+ if (!sidebarComponent) {
151
+ ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, renderSidebarWidgetLines(update), { placement: "aboveEditor" });
152
+ }
168
153
  },
169
154
  close(): void {
170
155
  if (closed) {
@@ -172,24 +157,24 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
172
157
  }
173
158
  closed = true;
174
159
  ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, undefined);
175
- if (overlayDone) {
176
- overlayDone(undefined);
177
- } else {
178
- overlayHandle?.hide();
179
- }
180
- overlayComponent = undefined;
181
- overlayTui = undefined;
182
- overlayDone = undefined;
183
- overlayHandle = undefined;
160
+ sidebarComponent = undefined;
161
+ sidebarTui = undefined;
184
162
  },
185
163
  };
186
164
  }
187
165
 
188
- function supportsTuiOverlay(ctx: UiContext): boolean {
166
+ function supportsTuiWidget(ctx: UiContext): boolean {
189
167
  const mode = (ctx as UiContext & { mode?: string }).mode;
190
168
  return mode === "tui" || mode === undefined;
191
169
  }
192
170
 
171
+ function sidebarWidgetLineLimit(tui: TUI): number {
172
+ const terminalRows = tui.terminal.rows;
173
+ const rows = Number.isFinite(terminalRows) ? Math.max(0, Math.floor(terminalRows)) : 24;
174
+ const availableRows = rows - SIDEBAR_WIDGET_RESERVED_INPUT_ROWS;
175
+ return Math.max(SIDEBAR_WIDGET_MIN_ROWS, Math.min(SIDEBAR_WIDGET_MAX_ROWS, availableRows));
176
+ }
177
+
193
178
  function renderSidebarWidgetLines(update: CoordinatorProgressUpdate): string[] {
194
179
  const progress = update.taskProgress;
195
180
  const summary = progress?.summary;
@@ -229,6 +214,30 @@ function renderSidebarOverlayLines(
229
214
  return rows.map((row) => sidebarPanelRow(row, contentWidth, theme));
230
215
  }
231
216
 
217
+ function limitSidebarPanelLines(lines: string[], theme: Theme, width: number, maxRows: number | undefined): string[] {
218
+ if (maxRows === undefined || !Number.isFinite(maxRows)) {
219
+ return lines;
220
+ }
221
+
222
+ const limit = Math.max(0, Math.floor(maxRows));
223
+ if (lines.length <= limit) {
224
+ return lines;
225
+ }
226
+ if (limit === 0) {
227
+ return [];
228
+ }
229
+
230
+ const contentWidth = Math.max(8, Math.max(28, width) - 4);
231
+ if (limit === 1) {
232
+ return [sidebarPanelRow(theme.fg("dim", "…"), contentWidth, theme)];
233
+ }
234
+
235
+ const visibleLines = lines.slice(0, limit - 1);
236
+ const omitted = lines.length - visibleLines.length;
237
+ visibleLines.push(sidebarPanelRow(theme.fg("dim", `… ${omitted} more`), contentWidth, theme));
238
+ return visibleLines;
239
+ }
240
+
232
241
  function renderSidebarRows(update: CoordinatorProgressUpdate | undefined, theme: Theme, width: number): string[] {
233
242
  if (!update) {
234
243
  return ["", sidebarHeading("Pi Long Task", theme), "", theme.fg("muted", "Preparing long-task sidebar...")];