@nmakarov/cli-toolkit 0.68.0 → 0.70.0

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/dist/index.js CHANGED
@@ -88,9 +88,48 @@ var init_components = __esm({
88
88
  }
89
89
  });
90
90
 
91
+ // src/screen/scrollbar.js
92
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
93
+ const view = Math.max(1, Math.floor(viewportRows));
94
+ const total = Math.max(0, Math.floor(totalLines));
95
+ if (total <= view) return null;
96
+ const maxScroll = total - view;
97
+ const thumbSize = Math.max(1, Math.round(view / total * view));
98
+ const travel = Math.max(0, view - thumbSize);
99
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
100
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
101
+ const glyphs = [];
102
+ for (let i = 0; i < view; i++) {
103
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
104
+ }
105
+ return glyphs;
106
+ }
107
+ var BAR_THUMB, BAR_TRACK, PAGE_SCROLL_KEY_BINDINGS;
108
+ var init_scrollbar = __esm({
109
+ "src/screen/scrollbar.js"() {
110
+ BAR_THUMB = "#";
111
+ BAR_TRACK = "|";
112
+ PAGE_SCROLL_KEY_BINDINGS = [
113
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
114
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
115
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
116
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
117
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
118
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
119
+ ];
120
+ }
121
+ });
122
+
91
123
  // src/screen/list-components.js
92
124
  import React, { useState, useEffect, useRef, createElement } from "react";
93
125
  import { Box as Box2, Text as Text2 } from "ink";
126
+ function clipNameForScrollbar(name, needsBar) {
127
+ if (!needsBar || name == null) return name;
128
+ const s = String(name);
129
+ if (s.length <= 1) return s;
130
+ if (/\s$/.test(s)) return s.slice(0, -1);
131
+ return s;
132
+ }
94
133
  function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
95
134
  const [, forceUpdate] = useState({});
96
135
  const termWidth = (process.stdout.columns || 80) - 8;
@@ -304,17 +343,42 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
304
343
  });
305
344
  ctx.setAction("scrollUp", () => {
306
345
  const { scrollOffset: currentScrollOffset } = scrollStateRef.current;
307
- const newScrollOffset = Math.max(0, currentScrollOffset - 1);
308
- setScrollOffset(newScrollOffset);
346
+ setScrollOffset(Math.max(0, currentScrollOffset - 1));
309
347
  forceUpdate({});
310
348
  });
311
349
  ctx.setAction("scrollDown", () => {
312
350
  const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
313
351
  const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
314
- const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
315
- setScrollOffset(newScrollOffset);
352
+ setScrollOffset(Math.min(currentMaxScrollOffset, currentScrollOffset + 1));
353
+ forceUpdate({});
354
+ });
355
+ ctx.setAction("pageUp", () => {
356
+ const { maxHeight: vh } = scrollStateRef.current;
357
+ const page = Math.max(1, vh || 1);
358
+ const newIndex = Math.max(0, selectedIndexRef.current - page);
359
+ selectedIndexRef.current = newIndex;
360
+ const list = displayItemsRef.current;
361
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
362
+ setScrollOffset(newIndex);
363
+ forceUpdate({});
364
+ });
365
+ ctx.setAction("pageDown", () => {
366
+ const { maxHeight: vh, totalItems } = scrollStateRef.current;
367
+ const page = Math.max(1, vh || 1);
368
+ const maxIndex = Math.max(0, totalItems - 1);
369
+ const newIndex = Math.min(maxIndex, selectedIndexRef.current + page);
370
+ selectedIndexRef.current = newIndex;
371
+ const list = displayItemsRef.current;
372
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
373
+ const maxScroll = Math.max(0, totalItems - page);
374
+ setScrollOffset(Math.min(maxScroll, Math.max(0, newIndex - page + 1)));
316
375
  forceUpdate({});
317
376
  });
377
+ const navBindings = [
378
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
379
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
380
+ ...PAGE_SCROLL_KEY_BINDINGS
381
+ ];
318
382
  if (sortable) {
319
383
  ctx.setAction("toggleSort", () => {
320
384
  const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
@@ -361,8 +425,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
361
425
  }
362
426
  };
363
427
  ctx.setKeyBinding([
364
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
365
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
428
+ ...navBindings,
366
429
  {
367
430
  key: "s",
368
431
  caption: sortCaption,
@@ -372,47 +435,47 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
372
435
  ]);
373
436
  ctx.update();
374
437
  } else {
375
- ctx.setKeyBinding([
376
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
377
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
378
- ]);
438
+ ctx.setKeyBinding(navBindings);
379
439
  }
380
440
  }, [sortOrder, sortable]);
381
441
  const selectedIndex = selectedIndexRef.current;
442
+ const needsBar = displayItems.length > effectiveMaxHeight;
443
+ const barGlyphs = needsBar ? scrollbarGlyphs(effectiveMaxHeight, displayItems.length, clampedScrollOffset) : null;
444
+ const appendBar = (rowContent, displayIndex) => {
445
+ if (!barGlyphs) return rowContent;
446
+ const glyph = barGlyphs[displayIndex] ?? BAR_TRACK;
447
+ return h2(
448
+ Box2,
449
+ { flexDirection: "row" },
450
+ rowContent,
451
+ h2(Text2, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
452
+ );
453
+ };
382
454
  const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
383
455
  const isFirstVisible = displayIndex === 0;
384
456
  const isLastVisible = displayIndex === visibleItems.length - 1;
385
- let arrowPrefix = "";
386
- let selectionPrefix = "";
387
- if (isFirstVisible && canScrollUp) {
388
- arrowPrefix = "\u2191 ";
389
- } else if (isLastVisible && canScrollDown) {
390
- arrowPrefix = "\u2193 ";
391
- } else {
392
- arrowPrefix = " ";
393
- }
394
- if (isSelected) {
395
- selectionPrefix = selectionMarker;
396
- } else {
397
- selectionPrefix = " ".repeat(selectionMarker.length);
457
+ let arrowPrefix = " ";
458
+ if (!needsBar) {
459
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
460
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
398
461
  }
462
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
463
+ const label = clipNameForScrollbar(item.name, needsBar);
399
464
  return h2(
400
465
  Box2,
401
466
  { flexDirection: "row" },
402
- // Arrow (clickable if functional, not highlighted)
403
- h2(Text2, {
404
- key: `arrow-${actualIndex}`,
405
- color: "white"
406
- }, arrowPrefix),
407
- // Selection marker space (always same width, not highlighted)
467
+ h2(Text2, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
408
468
  h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
409
- // Item name (highlighted if selected)
410
- h2(Text2, {
411
- key: `name-${actualIndex}`,
412
- color: isSelected ? "black" : "white",
413
- backgroundColor: isSelected ? "cyan" : void 0,
414
- bold: isSelected
415
- }, item.name)
469
+ h2(
470
+ Text2,
471
+ {
472
+ key: `name-${actualIndex}`,
473
+ color: isSelected ? "black" : "white",
474
+ backgroundColor: isSelected ? "cyan" : void 0,
475
+ bold: isSelected
476
+ },
477
+ label
478
+ )
416
479
  );
417
480
  };
418
481
  const itemRenderer = renderItem || defaultRenderItem;
@@ -425,42 +488,32 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
425
488
  if (renderItem) {
426
489
  const isFirstVisible = displayIndex === 0;
427
490
  const isLastVisible = displayIndex === visibleItems.length - 1;
428
- let arrowPrefix = "";
429
- let selectionPrefix = "";
430
- if (isFirstVisible && canScrollUp) {
431
- arrowPrefix = "\u2191 ";
432
- } else if (isLastVisible && canScrollDown) {
433
- arrowPrefix = "\u2193 ";
434
- } else {
435
- arrowPrefix = " ";
491
+ let arrowPrefix = " ";
492
+ if (!needsBar) {
493
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
494
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
436
495
  }
437
- if (isSelected) {
438
- selectionPrefix = selectionMarker;
439
- } else {
440
- selectionPrefix = " ".repeat(selectionMarker.length);
441
- }
442
- return h2(ScreenRow, {
443
- key: `item-${actualIndex}`,
444
- children: h2(
445
- Box2,
446
- { flexDirection: "row" },
447
- // Arrow (clickable if functional, not highlighted)
448
- h2(Text2, {
449
- key: `arrow-${actualIndex}`,
450
- color: "white"
451
- }, arrowPrefix),
452
- // Selection marker space (always same width, not highlighted)
453
- h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
454
- // Custom rendered content
455
- renderItem(item, isSelected, displayIndex)
456
- )
457
- });
458
- } else {
496
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
497
+ const clipped = needsBar ? { ...item, name: clipNameForScrollbar(item.name, true) } : item;
498
+ const row = h2(
499
+ Box2,
500
+ { flexDirection: "row" },
501
+ h2(Text2, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
502
+ h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
503
+ renderItem(clipped, isSelected, displayIndex)
504
+ );
459
505
  return h2(ScreenRow, {
460
506
  key: `item-${actualIndex}`,
461
- children: itemRenderer(item, isSelected, displayIndex, actualIndex)
507
+ children: appendBar(row, displayIndex)
462
508
  });
463
509
  }
510
+ return h2(ScreenRow, {
511
+ key: `item-${actualIndex}`,
512
+ children: appendBar(
513
+ itemRenderer(item, isSelected, displayIndex, actualIndex),
514
+ displayIndex
515
+ )
516
+ });
464
517
  })
465
518
  );
466
519
  }
@@ -468,6 +521,7 @@ var h2;
468
521
  var init_list_components = __esm({
469
522
  "src/screen/list-components.js"() {
470
523
  init_components();
524
+ init_scrollbar();
471
525
  h2 = createElement;
472
526
  }
473
527
  });
@@ -962,21 +1016,6 @@ function wrapTextLines(text, cols) {
962
1016
  }
963
1017
  return out;
964
1018
  }
965
- function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
966
- const view = Math.max(1, Math.floor(viewportRows));
967
- const total = Math.max(0, Math.floor(totalLines));
968
- if (total <= view) return null;
969
- const maxScroll = total - view;
970
- const thumbSize = Math.max(1, Math.round(view / total * view));
971
- const travel = Math.max(0, view - thumbSize);
972
- const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
973
- const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
974
- const glyphs = [];
975
- for (let i = 0; i < view; i++) {
976
- glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? "\u2588" : "\u2502");
977
- }
978
- return glyphs;
979
- }
980
1019
  function padEndVisible(s, w) {
981
1020
  const t = String(s ?? "");
982
1021
  if (t.length >= w) return t.slice(0, w);
@@ -995,20 +1034,19 @@ function ScrollableText({
995
1034
  }) {
996
1035
  const [scrollTop, setScrollTop] = useState3(0);
997
1036
  const [, bump] = useState3(0);
998
- const termCols = process.stdout.columns || 80;
999
1037
  const termRows = process.stdout.rows || 24;
1000
1038
  const viewportRows = Math.max(
1001
1039
  4,
1002
1040
  maxHeight != null ? Math.floor(maxHeight) : Math.max(8, termRows - 8)
1003
1041
  );
1004
- const provisionalBar = showScrollbar ? 1 : 0;
1005
- const wrapCols = Math.max(8, termCols - provisionalBar);
1042
+ const contentCols = Math.max(20, getScreenWidth() - 4);
1043
+ const barCols = showScrollbar ? 1 : 0;
1044
+ const textWidth = Math.max(8, contentCols - barCols);
1006
1045
  const allLines = useMemo(() => {
1007
1046
  if (Array.isArray(linesProp)) return linesProp.map((l) => String(l ?? ""));
1008
- return wrap ? wrapTextLines(text, wrapCols) : String(text ?? "").split("\n");
1009
- }, [linesProp, text, wrap, wrapCols]);
1047
+ return wrap ? wrapTextLines(text, textWidth) : String(text ?? "").split("\n");
1048
+ }, [linesProp, text, wrap, textWidth]);
1010
1049
  const needsBar = showScrollbar && allLines.length > viewportRows;
1011
- const textWidth = Math.max(8, termCols - (needsBar ? 1 : 0));
1012
1050
  const maxScroll = Math.max(0, allLines.length - viewportRows);
1013
1051
  const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
1014
1052
  const visible = allLines.slice(clamped, clamped + viewportRows);
@@ -1048,22 +1086,24 @@ function ScrollableText({
1048
1086
  const status = allLines.length === 0 ? "empty" : `lines ${clamped + 1}-${Math.min(clamped + visible.length, allLines.length)} of ${allLines.length}` + (needsBar ? " \xB7 \u2325\u2191/\u2193 or PgUp/Dn page" : "");
1049
1087
  const rowNodes = visible.map((line, i) => {
1050
1088
  const body = padEndVisible(line, textWidth);
1051
- const glyph = bar ? bar[i] ?? "\u2502" : "";
1089
+ const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
1090
+ const isThumb = glyph === BAR_THUMB;
1052
1091
  return h5(
1053
- Box4,
1054
- { key: `L${clamped + i}`, flexDirection: "row" },
1055
- h5(Text5, {}, body),
1056
- glyph ? h5(Text5, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, glyph) : null
1092
+ Text5,
1093
+ { key: `L${clamped + i}` },
1094
+ body,
1095
+ glyph ? h5(Text5, { color: isThumb ? "cyan" : "gray" }, glyph) : null
1057
1096
  );
1058
1097
  });
1059
1098
  if (bar && visible.length < viewportRows) {
1060
1099
  for (let i = visible.length; i < viewportRows; i++) {
1100
+ const glyph = bar[i] ?? BAR_TRACK;
1061
1101
  rowNodes.push(
1062
1102
  h5(
1063
- Box4,
1064
- { key: `pad${i}`, flexDirection: "row" },
1065
- h5(Text5, {}, padEndVisible("", textWidth)),
1066
- h5(Text5, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, bar[i] ?? "\u2502")
1103
+ Text5,
1104
+ { key: `pad${i}` },
1105
+ padEndVisible("", textWidth),
1106
+ h5(Text5, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
1067
1107
  )
1068
1108
  );
1069
1109
  }
@@ -1079,16 +1119,14 @@ function ScrollableText({
1079
1119
  var h5, SCROLL_KEYS;
1080
1120
  var init_scrollable_text = __esm({
1081
1121
  "src/screen/scrollable-text.js"() {
1122
+ init_components();
1123
+ init_scrollbar();
1124
+ init_scrollbar();
1082
1125
  h5 = createElement2;
1083
1126
  SCROLL_KEYS = [
1084
1127
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1085
1128
  { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1086
- { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1087
- { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1088
- { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1089
- { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1090
- { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1091
- { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1129
+ ...PAGE_SCROLL_KEY_BINDINGS
1092
1130
  ];
1093
1131
  }
1094
1132
  });
@@ -1241,6 +1279,7 @@ var init_screen = __esm({
1241
1279
  init_components();
1242
1280
  init_ui_elements();
1243
1281
  init_scrollable_text();
1282
+ init_scrollbar();
1244
1283
  init_key_bindings();
1245
1284
  init_utils();
1246
1285
  init_footer_builder();
@@ -7014,12 +7053,13 @@ function tasksSchemaSpec(queueName = "tasks") {
7014
7053
  }
7015
7054
  };
7016
7055
  }
7017
- function taskHistoryInsertFromQueueRow(row, overrides) {
7056
+ function taskHistoryInsertFromQueueRow(row, overrides = {}) {
7018
7057
  const { id, ...snapshot } = row;
7019
- void id;
7058
+ const opid = overrides.opid !== void 0 ? overrides.opid : snapshot.opid != null && String(snapshot.opid).trim() !== "" ? snapshot.opid : id;
7020
7059
  return {
7021
7060
  ...snapshot,
7022
- ...overrides
7061
+ ...overrides,
7062
+ opid
7023
7063
  };
7024
7064
  }
7025
7065
  async function ensureTaskTables(context, options = {}) {
@@ -8282,69 +8322,6 @@ var TaskStopRunner = class extends AbstractTask {
8282
8322
  }
8283
8323
  };
8284
8324
 
8285
- // src/tasks/coreTasks/TaskGetLogs.js
8286
- var TaskGetLogs = class extends AbstractTask {
8287
- /**
8288
- * @param {object} context
8289
- * @param {Record<string, unknown>} [overrides]
8290
- * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
8291
- */
8292
- static async resolveCustomParams(context, overrides = {}) {
8293
- const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
8294
- source: "string",
8295
- resource: "string",
8296
- tail: "number default 100",
8297
- afterTs: "string"
8298
- }, overrides);
8299
- const source = typeof merged.source === "string" ? merged.source.trim() : "";
8300
- const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
8301
- if (!source) throw new ParamError('getLogs: param "source" is required');
8302
- if (!resource) throw new ParamError('getLogs: param "resource" is required');
8303
- let tail = Number(merged.tail);
8304
- if (!Number.isFinite(tail) || tail < 1) tail = 100;
8305
- tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
8306
- const out = { source, resource, tail };
8307
- if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
8308
- out.afterTs = merged.afterTs.trim();
8309
- }
8310
- return out;
8311
- }
8312
- /**
8313
- * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
8314
- * @returns {Promise<{ success: boolean, results: unknown }>}
8315
- */
8316
- async run(_reportProgress) {
8317
- const p = this.task.params ?? {};
8318
- const source = String(p.source ?? "").trim();
8319
- const resource = String(p.resource ?? "").trim();
8320
- const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
8321
- const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
8322
- if (!source || !resource) {
8323
- return {
8324
- success: false,
8325
- results: { error: 'getLogs requires params "source" and "resource"' }
8326
- };
8327
- }
8328
- try {
8329
- const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
8330
- source,
8331
- resource,
8332
- tail,
8333
- afterTs
8334
- });
8335
- return {
8336
- success: true,
8337
- results: { records, latestTs, source, resource }
8338
- };
8339
- } catch (e) {
8340
- return {
8341
- success: false,
8342
- results: { error: e?.message ?? String(e) }
8343
- };
8344
- }
8345
- }
8346
- };
8347
-
8348
8325
  // src/tasks/runtimeParams.js
8349
8326
  var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
8350
8327
  var LOGGER_RUNTIME_KEYS = [
@@ -8358,7 +8335,16 @@ var LOGGER_RUNTIME_KEYS = [
8358
8335
  "progressWithTimes",
8359
8336
  "progressThrottleMs"
8360
8337
  ];
8361
- var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
8338
+ var CONTROL_LANE_TASK_NAMES = [
8339
+ "stopRunner",
8340
+ "stop",
8341
+ "pauseRunner",
8342
+ "pause",
8343
+ "unpauseRunner",
8344
+ "unpause",
8345
+ "setRuntimeParam",
8346
+ "setRunnerParam"
8347
+ ];
8362
8348
  function controlLaneTaskNames(extra) {
8363
8349
  const names = [...CONTROL_LANE_TASK_NAMES];
8364
8350
  if (extra == null || extra === "") return names;
@@ -8421,6 +8407,7 @@ function ensureTasksRuntime(context, seed = {}) {
8421
8407
  if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
8422
8408
  if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
8423
8409
  if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
8410
+ if (rt.paused === void 0) rt.paused = seed.paused === true;
8424
8411
  return rt;
8425
8412
  }
8426
8413
  async function applyRuntimeParam(context, key, value) {
@@ -8485,6 +8472,140 @@ function readLoopRuntime(context) {
8485
8472
  };
8486
8473
  }
8487
8474
 
8475
+ // src/tasks/coreTasks/TaskPauseRunner.js
8476
+ async function applyRunnerPaused(context, paused) {
8477
+ const runtime = ensureTasksRuntime(context);
8478
+ const was = runtime.paused === true;
8479
+ runtime.paused = paused === true;
8480
+ const registry = context.servicesRegistry;
8481
+ if (registry && typeof registry === "object") {
8482
+ await updateServicesRegistryMetadata(context, registry, {
8483
+ paused: runtime.paused,
8484
+ pausedAt: runtime.paused ? (/* @__PURE__ */ new Date()).toISOString() : null
8485
+ });
8486
+ }
8487
+ const message = runtime.paused ? was ? "Runner already paused (no new worker tasks)." : "Runner paused: finishing in-flight work; no new worker tasks until unpause." : was ? "Runner unpaused: claiming worker tasks again." : "Runner already running (not paused).";
8488
+ context.logger?.warn?.(
8489
+ runtime.paused ? `[TaskPauseRunner] ${message}` : `[TaskUnpauseRunner] ${message}`
8490
+ );
8491
+ return {
8492
+ success: true,
8493
+ results: {
8494
+ paused: runtime.paused,
8495
+ message
8496
+ }
8497
+ };
8498
+ }
8499
+ var TaskPauseRunner = class extends AbstractTask {
8500
+ static taskName = "pauseRunner";
8501
+ static description = "Pause a runner: finish in-flight tasks, claim no new worker tasks until unpaused";
8502
+ static aliases = ["pause"];
8503
+ static defaultWaitForResult = true;
8504
+ /**
8505
+ * @param {object} context
8506
+ * @param {Record<string, unknown>} [overrides]
8507
+ * @returns {Promise<object>}
8508
+ */
8509
+ static async resolveParams(context, overrides = {}) {
8510
+ const main = await super.resolveParams(context, overrides);
8511
+ if (!main.serviceName) {
8512
+ throw new ParamError(
8513
+ "pause/pauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
8514
+ );
8515
+ }
8516
+ return main;
8517
+ }
8518
+ async run() {
8519
+ return applyRunnerPaused(this.context, true);
8520
+ }
8521
+ };
8522
+ var TaskUnpauseRunner = class extends AbstractTask {
8523
+ static taskName = "unpauseRunner";
8524
+ static description = "Unpause a runner: resume claiming worker tasks";
8525
+ static aliases = ["unpause"];
8526
+ static defaultWaitForResult = true;
8527
+ /**
8528
+ * @param {object} context
8529
+ * @param {Record<string, unknown>} [overrides]
8530
+ * @returns {Promise<object>}
8531
+ */
8532
+ static async resolveParams(context, overrides = {}) {
8533
+ const main = await super.resolveParams(context, overrides);
8534
+ if (!main.serviceName) {
8535
+ throw new ParamError(
8536
+ "unpause/unpauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
8537
+ );
8538
+ }
8539
+ return main;
8540
+ }
8541
+ async run() {
8542
+ return applyRunnerPaused(this.context, false);
8543
+ }
8544
+ };
8545
+
8546
+ // src/tasks/coreTasks/TaskGetLogs.js
8547
+ var TaskGetLogs = class extends AbstractTask {
8548
+ /**
8549
+ * @param {object} context
8550
+ * @param {Record<string, unknown>} [overrides]
8551
+ * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
8552
+ */
8553
+ static async resolveCustomParams(context, overrides = {}) {
8554
+ const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
8555
+ source: "string",
8556
+ resource: "string",
8557
+ tail: "number default 100",
8558
+ afterTs: "string"
8559
+ }, overrides);
8560
+ const source = typeof merged.source === "string" ? merged.source.trim() : "";
8561
+ const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
8562
+ if (!source) throw new ParamError('getLogs: param "source" is required');
8563
+ if (!resource) throw new ParamError('getLogs: param "resource" is required');
8564
+ let tail = Number(merged.tail);
8565
+ if (!Number.isFinite(tail) || tail < 1) tail = 100;
8566
+ tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
8567
+ const out = { source, resource, tail };
8568
+ if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
8569
+ out.afterTs = merged.afterTs.trim();
8570
+ }
8571
+ return out;
8572
+ }
8573
+ /**
8574
+ * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
8575
+ * @returns {Promise<{ success: boolean, results: unknown }>}
8576
+ */
8577
+ async run(_reportProgress) {
8578
+ const p = this.task.params ?? {};
8579
+ const source = String(p.source ?? "").trim();
8580
+ const resource = String(p.resource ?? "").trim();
8581
+ const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
8582
+ const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
8583
+ if (!source || !resource) {
8584
+ return {
8585
+ success: false,
8586
+ results: { error: 'getLogs requires params "source" and "resource"' }
8587
+ };
8588
+ }
8589
+ try {
8590
+ const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
8591
+ source,
8592
+ resource,
8593
+ tail,
8594
+ afterTs
8595
+ });
8596
+ return {
8597
+ success: true,
8598
+ results: { records, latestTs, source, resource }
8599
+ };
8600
+ } catch (e) {
8601
+ return {
8602
+ success: false,
8603
+ results: { error: e?.message ?? String(e) }
8604
+ };
8605
+ }
8606
+ }
8607
+ };
8608
+
8488
8609
  // src/tasks/coreTasks/TaskSetRuntimeParam.js
8489
8610
  var TaskSetRuntimeParam = class extends AbstractTask {
8490
8611
  static defaultWaitForResult = true;
@@ -8640,7 +8761,7 @@ var TasksRegistry = class _TasksRegistry {
8640
8761
  * @returns {TasksRegistry}
8641
8762
  */
8642
8763
  static withCoreTasks() {
8643
- return new _TasksRegistry().add("ping", TaskPing).add("sampleProcess", TaskSampleProcess).add("shellCommand", TaskShellCommand).add("systemInfo", TaskSystemInfo).add("info", TaskSystemInfo).add("taskSumAB", TaskSumAB).add("stopRunner", TaskStopRunner).add("stop", TaskStopRunner).add("getLogs", TaskGetLogs).add("setRuntimeParam", TaskSetRuntimeParam).add("setRunnerParam", TaskSetRuntimeParam);
8764
+ return new _TasksRegistry().add("ping", TaskPing).add("sampleProcess", TaskSampleProcess).add("shellCommand", TaskShellCommand).add("systemInfo", TaskSystemInfo).add("info", TaskSystemInfo).add("taskSumAB", TaskSumAB).add("stopRunner", TaskStopRunner).add("stop", TaskStopRunner).add("pauseRunner", TaskPauseRunner).add("pause", TaskPauseRunner).add("unpauseRunner", TaskUnpauseRunner).add("unpause", TaskUnpauseRunner).add("getLogs", TaskGetLogs).add("setRuntimeParam", TaskSetRuntimeParam).add("setRunnerParam", TaskSetRuntimeParam);
8644
8765
  }
8645
8766
  /**
8646
8767
  * Register a single task class under a name. Overwrites any previous entry.
@@ -8743,6 +8864,10 @@ var SERVICE_TASK_NAMES = [
8743
8864
  "ping",
8744
8865
  "stop",
8745
8866
  "stopRunner",
8867
+ "pause",
8868
+ "pauseRunner",
8869
+ "unpause",
8870
+ "unpauseRunner",
8746
8871
  "shellCommand",
8747
8872
  "systemInfo",
8748
8873
  "info",
@@ -9208,6 +9333,7 @@ async function runTasksLoop(context, options) {
9208
9333
  const defaultMeta = {
9209
9334
  component: "tasks-runner",
9210
9335
  allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
9336
+ paused: false,
9211
9337
  runtime: {
9212
9338
  maxParallel: loop0.maxParallel,
9213
9339
  pollMs: loop0.pollMs,
@@ -9274,28 +9400,38 @@ async function runTasksLoop(context, options) {
9274
9400
  if (claimJitterMs > 0) {
9275
9401
  await sleepMs(Math.floor(Math.random() * (claimJitterMs + 1)));
9276
9402
  }
9277
- while (runningPromises.size < maxParallel) {
9278
- const claimed = await claimNextRunnableTask(
9279
- context,
9280
- tasksTable,
9281
- target,
9282
- registry,
9283
- scanLimit,
9284
- allowedTasks,
9285
- runnerIdentity
9286
- );
9287
- if (!claimed) break;
9288
- const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
9289
- if (outcome.stopRunnerRequested && !stopRequested) {
9290
- stopRequested = true;
9291
- stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
9292
- context.tasksRunnerStop = true;
9293
- await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
9294
- }
9295
- }).finally(() => {
9296
- runningPromises.delete(p);
9297
- });
9298
- runningPromises.add(p);
9403
+ const paused = context.tasksRuntime?.paused === true;
9404
+ if (!paused) {
9405
+ while (runningPromises.size < maxParallel) {
9406
+ const claimed = await claimNextRunnableTask(
9407
+ context,
9408
+ tasksTable,
9409
+ target,
9410
+ registry,
9411
+ scanLimit,
9412
+ allowedTasks,
9413
+ runnerIdentity
9414
+ );
9415
+ if (!claimed) break;
9416
+ const p = executeClaimedTask(
9417
+ context,
9418
+ tasksTable,
9419
+ historyTable,
9420
+ claimed,
9421
+ registry,
9422
+ runningTaskInstances
9423
+ ).then(async (outcome) => {
9424
+ if (outcome.stopRunnerRequested && !stopRequested) {
9425
+ stopRequested = true;
9426
+ stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
9427
+ context.tasksRunnerStop = true;
9428
+ await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
9429
+ }
9430
+ }).finally(() => {
9431
+ runningPromises.delete(p);
9432
+ });
9433
+ runningPromises.add(p);
9434
+ }
9299
9435
  }
9300
9436
  const wakePromises = [...runningPromises];
9301
9437
  if (runningControlPromise) {
@@ -9349,16 +9485,26 @@ async function waitForTaskResult(context, taskId, options = {}) {
9349
9485
  const pollMs = options.pollMs ?? 500;
9350
9486
  const { tasksTable, historyTable } = queueToTableNames(queueName);
9351
9487
  const deadline = Date.now() + timeoutMs;
9352
- const waitStartedAt = /* @__PURE__ */ new Date();
9353
- let cachedNameOpid = null;
9354
- async function historySinceWait(name, opid) {
9355
- let q = db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
9488
+ const waitStartedAt = new Date(Date.now() - 5e3);
9489
+ let cachedNameOpid = options.name != null && String(options.name).trim() !== "" ? {
9490
+ name: String(options.name).trim(),
9491
+ opid: options.opid !== void 0 ? options.opid : null
9492
+ } : null;
9493
+ async function findHistory(name, opid) {
9494
+ const base = () => db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
9495
+ if (opid != null && String(opid).trim() !== "") {
9496
+ const byOpid = await base().where({ opid }).orderBy("completed_at", "desc").first();
9497
+ if (byOpid) return byOpid;
9498
+ }
9499
+ const byQueueId = await base().where({ opid: taskId }).orderBy("completed_at", "desc").first();
9500
+ if (byQueueId) return byQueueId;
9501
+ const byQueueIdAny = await db(historyTable).where({ name, opid: taskId }).orderBy("completed_at", "desc").first();
9502
+ if (byQueueIdAny) return byQueueIdAny;
9356
9503
  if (opid == null || opid === "") {
9357
- q = q.whereNull("opid");
9358
- } else {
9359
- q = q.where({ opid });
9504
+ const byNull = await base().whereNull("opid").orderBy("completed_at", "desc").first();
9505
+ if (byNull) return byNull;
9360
9506
  }
9361
- return await q.orderBy("completed_at", "desc").first();
9507
+ return void 0;
9362
9508
  }
9363
9509
  while (Date.now() <= deadline) {
9364
9510
  const legacy = await db(historyTable).where({ id: taskId }).orderBy("created_at", "desc").first();
@@ -9368,18 +9514,17 @@ async function waitForTaskResult(context, taskId, options = {}) {
9368
9514
  const pending = await db(tasksTable).where({ id: taskId }).first();
9369
9515
  if (pending) {
9370
9516
  cachedNameOpid = { name: pending.name, opid: pending.opid };
9371
- const done = await historySinceWait(pending.name, pending.opid);
9372
- if (done) {
9373
- return done;
9374
- }
9375
- } else if (cachedNameOpid) {
9376
- const done = await historySinceWait(cachedNameOpid.name, cachedNameOpid.opid);
9517
+ }
9518
+ if (cachedNameOpid) {
9519
+ const done = await findHistory(cachedNameOpid.name, cachedNameOpid.opid);
9377
9520
  if (done) {
9378
9521
  return done;
9379
9522
  }
9380
- return null;
9381
9523
  } else {
9382
- return null;
9524
+ const byQueueId = await db(historyTable).where({ opid: taskId }).orderBy("completed_at", "desc").first();
9525
+ if (byQueueId) {
9526
+ return byQueueId;
9527
+ }
9383
9528
  }
9384
9529
  await sleepMs(pollMs);
9385
9530
  }
@@ -9521,6 +9666,8 @@ export {
9521
9666
  AbstractTask,
9522
9667
  Args,
9523
9668
  Aws,
9669
+ BAR_THUMB,
9670
+ BAR_TRACK,
9524
9671
  Box5 as Box,
9525
9672
  Db,
9526
9673
  Divider,
@@ -9535,6 +9682,7 @@ export {
9535
9682
  ListItem,
9536
9683
  MultiColumnListComponent,
9537
9684
  MultiColumnListWithPreviewComponent,
9685
+ PAGE_SCROLL_KEY_BINDINGS,
9538
9686
  Params,
9539
9687
  REMOTE_CLI_REL,
9540
9688
  React2 as React,
@@ -9548,6 +9696,7 @@ export {
9548
9696
  ScreenTitle,
9549
9697
  ScrollableText,
9550
9698
  TaskGetLogs,
9699
+ TaskPauseRunner,
9551
9700
  TaskPing,
9552
9701
  TaskSampleProcess,
9553
9702
  TaskSetRuntimeParam,
@@ -9555,6 +9704,7 @@ export {
9555
9704
  TaskStopRunner,
9556
9705
  TaskSumAB,
9557
9706
  TaskSystemInfo,
9707
+ TaskUnpauseRunner,
9558
9708
  TasksManager,
9559
9709
  TasksRegistry,
9560
9710
  Text6 as Text,
@@ -9562,6 +9712,7 @@ export {
9562
9712
  activateRelease,
9563
9713
  appendDeployLog,
9564
9714
  appendTaskIpcLog,
9715
+ applyRunnerPaused,
9565
9716
  applyRuntimeParam,
9566
9717
  applyRuntimePatch,
9567
9718
  bindingIdentity,