@yusukeshib/pi-babysit 0.6.23 → 0.6.25

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/README.md CHANGED
@@ -167,9 +167,13 @@ sessions, and click again to return to running-only. Both modes show newest firs
167
167
  ten per page; `< 1/4 >` shows the current/total page when needed. Click the
168
168
  arrows to page. Rows occupy one line, previewing the description (or the command
169
169
  for older processes and task for older agents), with `…` when clipped. Click an
170
- AGENT or PROCESS row to replace the list with a tall **log viewer in the same widget area**, not an
171
- overlay or separate screen. `Esc` or the right-hand **Close** button restores
172
- the list, preserving its mode and page. The viewer uses Pi's `ScrollView` for
170
+ AGENT or PROCESS row to expand a **log viewer directly below that row**. The summary
171
+ title, list header, and all other rows remain visible with their original colors.
172
+ Only one row is expanded at a time; selecting another closes the previous one.
173
+ The viewer is capped at ten lines including its existing row header and footer;
174
+ all saved history remains available by scrolling. Click the same row again to
175
+ collapse it, preserving the list's mode and page.
176
+ There is no separate Close button or Esc binding. The viewer uses Pi's `ScrollView` for
173
177
  scroll position and follow behavior, with a widget-local scrollbar styled using
174
178
  Pi's scrollbar theme. Wheel, scrollbar drag/track clicks, and ↑/↓ navigate the
175
179
  log without requiring any Pi core changes. Home/End and PageUp/PageDown remain
package/index.ts CHANGED
@@ -2600,6 +2600,7 @@ function renderWidgetSessionHeader(
2600
2600
  // How many trailing output lines to show per session in the widget.
2601
2601
  const WIDGET_COLLAPSED_TAIL_LINES = 1;
2602
2602
  const WIDGET_SESSION_PAGE_SIZE = 10;
2603
+ const WIDGET_VIEWER_MAX_LINES = 10;
2603
2604
 
2604
2605
  export interface WidgetSessionListState {
2605
2606
  mode: "running" | "all";
@@ -2624,7 +2625,7 @@ interface WidgetMouseEvent {
2624
2625
  }
2625
2626
 
2626
2627
  /**
2627
- * Compact session list. Item presses open a viewer; rows never expand inline.
2628
+ * Compact session list. Item presses toggle the session's inline viewer.
2628
2629
  */
2629
2630
  export function createWidgetComponent(
2630
2631
  summaryLines: string[],
@@ -2682,6 +2683,9 @@ export function createWidgetComponent(
2682
2683
  }
2683
2684
  return lines;
2684
2685
  },
2686
+ getSessionLine(id: string) {
2687
+ return targetByLine.findIndex((target) => target?.type === "session" && target.id === id);
2688
+ },
2685
2689
  handleMouse(event: WidgetMouseEvent) {
2686
2690
  if (event.button !== "left") return undefined;
2687
2691
  const target = targetByLine[event.y];
@@ -2726,7 +2730,19 @@ export function createSessionWidget(options: {
2726
2730
  let displays: WidgetSessionDisplay[] = [];
2727
2731
  let viewer: ReturnType<typeof createLogViewer> | undefined;
2728
2732
  let source: FileLogSource | undefined;
2729
- let list = createWidgetComponent([], [], open, listState);
2733
+ let summaryLines: string[] = [];
2734
+ let listHeight = 0;
2735
+ let viewerRow = -1;
2736
+ let viewerLines = 0;
2737
+ let dragging = false;
2738
+ let list = createWidgetComponent([], [], toggle, listState);
2739
+ function rebuildList() {
2740
+ list = createWidgetComponent(summaryLines, displays, toggle, listState);
2741
+ }
2742
+ function toggle(id: string) {
2743
+ if (selectedId === id) close();
2744
+ else open(id);
2745
+ }
2730
2746
  function refreshViewer() {
2731
2747
  if (!selectedId || !viewer) return;
2732
2748
  const log = options.readLog(selectedId);
@@ -2746,16 +2762,32 @@ export function createSessionWidget(options: {
2746
2762
  }
2747
2763
  function close() {
2748
2764
  selectedId = undefined;
2765
+ viewerRow = -1;
2766
+ viewerLines = 0;
2767
+ dragging = false;
2749
2768
  viewer?.dispose();
2750
2769
  viewer = undefined;
2751
2770
  source = undefined;
2752
2771
  options.requestRender();
2753
2772
  }
2754
2773
  function open(id: string) {
2774
+ if (!displays.some((session) => session.id === id)) {
2775
+ displays = [...displays, { id, header: id, tail: [], viewable: true, active: true }];
2776
+ }
2777
+ if (listState.mode === "running" && !displays.find((session) => session.id === id)?.active)
2778
+ listState.mode = "all";
2779
+ const visible = listState.mode === "running" ? displays.filter((session) => session.active) : displays;
2780
+ listState.page = Math.floor(visible.findIndex((session) => session.id === id) / WIDGET_SESSION_PAGE_SIZE);
2781
+ rebuildList();
2782
+ dragging = false;
2755
2783
  viewer?.dispose();
2756
2784
  source = undefined;
2757
2785
  selectedId = id;
2758
- viewer = createLogViewer({ ...options, onClose: close });
2786
+ viewer = createLogViewer({
2787
+ ...options,
2788
+ getHeight: () => Math.max(3, Math.min(WIDGET_VIEWER_MAX_LINES, options.getHeight() - listHeight + 1)),
2789
+ onClose: close,
2790
+ });
2759
2791
  refreshViewer();
2760
2792
  options.requestRender();
2761
2793
  }
@@ -2769,15 +2801,36 @@ export function createSessionWidget(options: {
2769
2801
  },
2770
2802
  update(summary: string[], sessions: WidgetSessionDisplay[]) {
2771
2803
  displays = sessions;
2772
- list = createWidgetComponent(summary, sessions, open, listState);
2804
+ summaryLines = summary;
2805
+ rebuildList();
2806
+ if (selectedId && !sessions.some((session) => session.id === selectedId && (listState.mode === "all" || session.active))) close();
2773
2807
  refreshViewer();
2774
2808
  options.requestRender();
2775
2809
  },
2776
- render(width: number) { return (viewer ?? list).render(width); },
2810
+ render(width: number) {
2811
+ const lines = list.render(width);
2812
+ listHeight = lines.length;
2813
+ viewerRow = selectedId ? list.getSessionLine(selectedId) : -1;
2814
+ viewerLines = 0;
2815
+ if (viewer && viewerRow >= 0) {
2816
+ // Keep the original styled row as the toggle header.
2817
+ const body = viewer.render(width).slice(1);
2818
+ viewerLines = body.length;
2819
+ lines.splice(viewerRow + 1, 0, ...body);
2820
+ }
2821
+ return lines;
2822
+ },
2777
2823
  handleMouse(event: TuiMouseEvent) {
2778
- // Stock Pi widget hosts forward mouse input to this component, not
2779
- // to a nested native layout tree. Keep the full wheel/drag event.
2780
- return (viewer ?? list).handleMouse(event);
2824
+ if (viewer && viewerRow >= 0 && (dragging || (event.y > viewerRow && event.y <= viewerRow + viewerLines))) {
2825
+ const result = viewer.handleMouse({ ...event, y: event.y - viewerRow });
2826
+ if (result && "capture" in result && result.capture) dragging = true;
2827
+ if (event.type === "release") dragging = false;
2828
+ return result;
2829
+ }
2830
+ const mode = listState.mode, page = listState.page;
2831
+ const result = list.handleMouse({ ...event, y: event.y > viewerRow && viewerRow >= 0 ? event.y - viewerLines : event.y });
2832
+ if (mode !== listState.mode || page !== listState.page) close();
2833
+ return result;
2781
2834
  },
2782
2835
  handleKey(data: string) { return viewer?.handleKey(data) ?? false; },
2783
2836
  invalidate() { list.invalidate(); viewer?.invalidate(); },
@@ -4186,7 +4239,7 @@ export default function (pi: ExtensionAPI) {
4186
4239
  : d.count && d.count > 1
4187
4240
  ? theme.fg("muted", `×${d.count}`)
4188
4241
  : "";
4189
- const header = theme.fg("muted", theme.bold("babysit_run COMMAND")) + (payload ? ` ${payload}` : "");
4242
+ const header = theme.fg("muted", theme.bold("babysit_run PROCESS")) + (payload ? ` ${payload}` : "");
4190
4243
  const box = new Box(1, 1, (t) => theme.bg("toolSuccessBg", t));
4191
4244
  box.addChild(new Text(header, 0, 0));
4192
4245
  box.addChild(new Text(renderStatus(status, theme), 0, 0));
@@ -4854,7 +4907,7 @@ export default function (pi: ExtensionAPI) {
4854
4907
  // Legacy calls without descriptions still render their original payload safely.
4855
4908
  renderCall(args, theme) {
4856
4909
  const isSubagent = args.profile === "subagent";
4857
- const kind = isSubagent ? "AGENT" : "COMMAND";
4910
+ const kind = isSubagent ? "AGENT" : "PROCESS";
4858
4911
  const payload = isSubagent ? args.task : args.command;
4859
4912
  const agent = isSubagent && args.agent ? ` [${escapeCommandForDisplay(args.agent)}]` : "";
4860
4913
  const title = theme.fg("muted", `${theme.bold(`babysit_run ${kind}`)}${agent}`);
package/log-viewer.ts CHANGED
@@ -244,21 +244,18 @@ export function createLogViewer(options: LogViewerOptions) {
244
244
  const heading = {
245
245
  invalidate() {},
246
246
  render(width: number) {
247
- const close = truncateToWidth("[Close]", width);
248
- const room = Math.max(0, width - visibleWidth(close) - 1);
249
- const title = truncateToWidth(sanitize(header).replace(/\n/g, " "), room);
247
+ const title = truncateToWidth(sanitize(header).replace(/\n/g, " "), width);
250
248
  return [
251
249
  options.getTheme().fg("accent", title) +
252
250
  " ".repeat(
253
- Math.max(0, width - visibleWidth(title) - visibleWidth(close)),
254
- ) +
255
- options.getTheme().fg("muted", close),
251
+ Math.max(0, width - visibleWidth(title)),
252
+ ),
256
253
  ];
257
254
  },
258
255
  handleMouse(e: TuiMouseEvent) {
259
256
  if (
260
257
  e.button !== "left" ||
261
- e.x < Math.max(0, e.width - 7) ||
258
+ e.x < 0 ||
262
259
  e.x >= e.width
263
260
  )
264
261
  return;
@@ -272,7 +269,7 @@ export function createLogViewer(options: LogViewerOptions) {
272
269
  const footer: Component = {
273
270
  invalidate() {},
274
271
  render: (width) => [
275
- options.getTheme().fg("dim", truncateToWidth(source ? `↑↓ · bytes ${pages[0]?.start ?? 0}–${pages.at(-1)?.end ?? 0}/${source.size} (window) · Esc close` : "↑↓ · Esc close", width)),
272
+ options.getTheme().fg("dim", truncateToWidth(source ? `↑↓ · bytes ${pages[0]?.start ?? 0}–${pages.at(-1)?.end ?? 0}/${source.size} (window)` : "↑↓", width)),
276
273
  ],
277
274
  };
278
275
  let geometry = {
@@ -489,10 +486,6 @@ export function createLogViewer(options: LogViewerOptions) {
489
486
  options.requestRender();
490
487
  },
491
488
  handleKey(data: string): boolean {
492
- if (matchesKey(data, Key.escape)) {
493
- options.onClose();
494
- return true;
495
- }
496
489
  if (matchesKey(data, Key.up)) scrollView.scrollBy(-1);
497
490
  else if (matchesKey(data, Key.down)) scrollView.scrollBy(1);
498
491
  else return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-babysit",
3
- "version": "0.6.23",
3
+ "version": "0.6.25",
4
4
  "description": "Run any shell command and pi subagents under babysit, with context-safe captured output.",
5
5
  "keywords": [
6
6
  "pi-package",