@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.
@@ -109,7 +109,46 @@ var init_components = __esm({
109
109
  }
110
110
  });
111
111
 
112
+ // src/screen/scrollbar.js
113
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
114
+ const view = Math.max(1, Math.floor(viewportRows));
115
+ const total = Math.max(0, Math.floor(totalLines));
116
+ if (total <= view) return null;
117
+ const maxScroll = total - view;
118
+ const thumbSize = Math.max(1, Math.round(view / total * view));
119
+ const travel = Math.max(0, view - thumbSize);
120
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
121
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
122
+ const glyphs = [];
123
+ for (let i = 0; i < view; i++) {
124
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
125
+ }
126
+ return glyphs;
127
+ }
128
+ var BAR_THUMB, BAR_TRACK, PAGE_SCROLL_KEY_BINDINGS;
129
+ var init_scrollbar = __esm({
130
+ "src/screen/scrollbar.js"() {
131
+ BAR_THUMB = "#";
132
+ BAR_TRACK = "|";
133
+ PAGE_SCROLL_KEY_BINDINGS = [
134
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
135
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
136
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
137
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
138
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
139
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
140
+ ];
141
+ }
142
+ });
143
+
112
144
  // src/screen/list-components.js
145
+ function clipNameForScrollbar(name, needsBar) {
146
+ if (!needsBar || name == null) return name;
147
+ const s = String(name);
148
+ if (s.length <= 1) return s;
149
+ if (/\s$/.test(s)) return s.slice(0, -1);
150
+ return s;
151
+ }
113
152
  function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
114
153
  const [, forceUpdate] = (0, import_react2.useState)({});
115
154
  const termWidth = (process.stdout.columns || 80) - 8;
@@ -323,17 +362,42 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
323
362
  });
324
363
  ctx.setAction("scrollUp", () => {
325
364
  const { scrollOffset: currentScrollOffset } = scrollStateRef.current;
326
- const newScrollOffset = Math.max(0, currentScrollOffset - 1);
327
- setScrollOffset(newScrollOffset);
365
+ setScrollOffset(Math.max(0, currentScrollOffset - 1));
328
366
  forceUpdate({});
329
367
  });
330
368
  ctx.setAction("scrollDown", () => {
331
369
  const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
332
370
  const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
333
- const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
334
- setScrollOffset(newScrollOffset);
371
+ setScrollOffset(Math.min(currentMaxScrollOffset, currentScrollOffset + 1));
372
+ forceUpdate({});
373
+ });
374
+ ctx.setAction("pageUp", () => {
375
+ const { maxHeight: vh } = scrollStateRef.current;
376
+ const page = Math.max(1, vh || 1);
377
+ const newIndex = Math.max(0, selectedIndexRef.current - page);
378
+ selectedIndexRef.current = newIndex;
379
+ const list = displayItemsRef.current;
380
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
381
+ setScrollOffset(newIndex);
335
382
  forceUpdate({});
336
383
  });
384
+ ctx.setAction("pageDown", () => {
385
+ const { maxHeight: vh, totalItems } = scrollStateRef.current;
386
+ const page = Math.max(1, vh || 1);
387
+ const maxIndex = Math.max(0, totalItems - 1);
388
+ const newIndex = Math.min(maxIndex, selectedIndexRef.current + page);
389
+ selectedIndexRef.current = newIndex;
390
+ const list = displayItemsRef.current;
391
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
392
+ const maxScroll = Math.max(0, totalItems - page);
393
+ setScrollOffset(Math.min(maxScroll, Math.max(0, newIndex - page + 1)));
394
+ forceUpdate({});
395
+ });
396
+ const navBindings = [
397
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
398
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
399
+ ...PAGE_SCROLL_KEY_BINDINGS
400
+ ];
337
401
  if (sortable) {
338
402
  ctx.setAction("toggleSort", () => {
339
403
  const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
@@ -380,8 +444,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
380
444
  }
381
445
  };
382
446
  ctx.setKeyBinding([
383
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
384
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
447
+ ...navBindings,
385
448
  {
386
449
  key: "s",
387
450
  caption: sortCaption,
@@ -391,47 +454,47 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
391
454
  ]);
392
455
  ctx.update();
393
456
  } else {
394
- ctx.setKeyBinding([
395
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
396
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
397
- ]);
457
+ ctx.setKeyBinding(navBindings);
398
458
  }
399
459
  }, [sortOrder, sortable]);
400
460
  const selectedIndex = selectedIndexRef.current;
461
+ const needsBar = displayItems.length > effectiveMaxHeight;
462
+ const barGlyphs = needsBar ? scrollbarGlyphs(effectiveMaxHeight, displayItems.length, clampedScrollOffset) : null;
463
+ const appendBar = (rowContent, displayIndex) => {
464
+ if (!barGlyphs) return rowContent;
465
+ const glyph = barGlyphs[displayIndex] ?? BAR_TRACK;
466
+ return h2(
467
+ import_ink2.Box,
468
+ { flexDirection: "row" },
469
+ rowContent,
470
+ h2(import_ink2.Text, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
471
+ );
472
+ };
401
473
  const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
402
474
  const isFirstVisible = displayIndex === 0;
403
475
  const isLastVisible = displayIndex === visibleItems.length - 1;
404
- let arrowPrefix = "";
405
- let selectionPrefix = "";
406
- if (isFirstVisible && canScrollUp) {
407
- arrowPrefix = "\u2191 ";
408
- } else if (isLastVisible && canScrollDown) {
409
- arrowPrefix = "\u2193 ";
410
- } else {
411
- arrowPrefix = " ";
412
- }
413
- if (isSelected) {
414
- selectionPrefix = selectionMarker;
415
- } else {
416
- selectionPrefix = " ".repeat(selectionMarker.length);
476
+ let arrowPrefix = " ";
477
+ if (!needsBar) {
478
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
479
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
417
480
  }
481
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
482
+ const label = clipNameForScrollbar(item.name, needsBar);
418
483
  return h2(
419
484
  import_ink2.Box,
420
485
  { flexDirection: "row" },
421
- // Arrow (clickable if functional, not highlighted)
422
- h2(import_ink2.Text, {
423
- key: `arrow-${actualIndex}`,
424
- color: "white"
425
- }, arrowPrefix),
426
- // Selection marker space (always same width, not highlighted)
486
+ h2(import_ink2.Text, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
427
487
  h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
428
- // Item name (highlighted if selected)
429
- h2(import_ink2.Text, {
430
- key: `name-${actualIndex}`,
431
- color: isSelected ? "black" : "white",
432
- backgroundColor: isSelected ? "cyan" : void 0,
433
- bold: isSelected
434
- }, item.name)
488
+ h2(
489
+ import_ink2.Text,
490
+ {
491
+ key: `name-${actualIndex}`,
492
+ color: isSelected ? "black" : "white",
493
+ backgroundColor: isSelected ? "cyan" : void 0,
494
+ bold: isSelected
495
+ },
496
+ label
497
+ )
435
498
  );
436
499
  };
437
500
  const itemRenderer = renderItem || defaultRenderItem;
@@ -444,42 +507,32 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
444
507
  if (renderItem) {
445
508
  const isFirstVisible = displayIndex === 0;
446
509
  const isLastVisible = displayIndex === visibleItems.length - 1;
447
- let arrowPrefix = "";
448
- let selectionPrefix = "";
449
- if (isFirstVisible && canScrollUp) {
450
- arrowPrefix = "\u2191 ";
451
- } else if (isLastVisible && canScrollDown) {
452
- arrowPrefix = "\u2193 ";
453
- } else {
454
- arrowPrefix = " ";
455
- }
456
- if (isSelected) {
457
- selectionPrefix = selectionMarker;
458
- } else {
459
- selectionPrefix = " ".repeat(selectionMarker.length);
510
+ let arrowPrefix = " ";
511
+ if (!needsBar) {
512
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
513
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
460
514
  }
515
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
516
+ const clipped = needsBar ? { ...item, name: clipNameForScrollbar(item.name, true) } : item;
517
+ const row = h2(
518
+ import_ink2.Box,
519
+ { flexDirection: "row" },
520
+ h2(import_ink2.Text, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
521
+ h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
522
+ renderItem(clipped, isSelected, displayIndex)
523
+ );
461
524
  return h2(ScreenRow, {
462
525
  key: `item-${actualIndex}`,
463
- children: h2(
464
- import_ink2.Box,
465
- { flexDirection: "row" },
466
- // Arrow (clickable if functional, not highlighted)
467
- h2(import_ink2.Text, {
468
- key: `arrow-${actualIndex}`,
469
- color: "white"
470
- }, arrowPrefix),
471
- // Selection marker space (always same width, not highlighted)
472
- h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
473
- // Custom rendered content
474
- renderItem(item, isSelected, displayIndex)
475
- )
476
- });
477
- } else {
478
- return h2(ScreenRow, {
479
- key: `item-${actualIndex}`,
480
- children: itemRenderer(item, isSelected, displayIndex, actualIndex)
526
+ children: appendBar(row, displayIndex)
481
527
  });
482
528
  }
529
+ return h2(ScreenRow, {
530
+ key: `item-${actualIndex}`,
531
+ children: appendBar(
532
+ itemRenderer(item, isSelected, displayIndex, actualIndex),
533
+ displayIndex
534
+ )
535
+ });
483
536
  })
484
537
  );
485
538
  }
@@ -489,6 +542,7 @@ var init_list_components = __esm({
489
542
  import_react2 = __toESM(require("react"), 1);
490
543
  import_ink2 = require("ink");
491
544
  init_components();
545
+ init_scrollbar();
492
546
  h2 = import_react2.createElement;
493
547
  }
494
548
  });
@@ -982,21 +1036,6 @@ function wrapTextLines(text, cols) {
982
1036
  }
983
1037
  return out;
984
1038
  }
985
- function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
986
- const view = Math.max(1, Math.floor(viewportRows));
987
- const total = Math.max(0, Math.floor(totalLines));
988
- if (total <= view) return null;
989
- const maxScroll = total - view;
990
- const thumbSize = Math.max(1, Math.round(view / total * view));
991
- const travel = Math.max(0, view - thumbSize);
992
- const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
993
- const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
994
- const glyphs = [];
995
- for (let i = 0; i < view; i++) {
996
- glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? "\u2588" : "\u2502");
997
- }
998
- return glyphs;
999
- }
1000
1039
  function padEndVisible(s, w) {
1001
1040
  const t = String(s ?? "");
1002
1041
  if (t.length >= w) return t.slice(0, w);
@@ -1015,20 +1054,19 @@ function ScrollableText({
1015
1054
  }) {
1016
1055
  const [scrollTop, setScrollTop] = (0, import_react5.useState)(0);
1017
1056
  const [, bump] = (0, import_react5.useState)(0);
1018
- const termCols = process.stdout.columns || 80;
1019
1057
  const termRows = process.stdout.rows || 24;
1020
1058
  const viewportRows = Math.max(
1021
1059
  4,
1022
1060
  maxHeight != null ? Math.floor(maxHeight) : Math.max(8, termRows - 8)
1023
1061
  );
1024
- const provisionalBar = showScrollbar ? 1 : 0;
1025
- const wrapCols = Math.max(8, termCols - provisionalBar);
1062
+ const contentCols = Math.max(20, getScreenWidth() - 4);
1063
+ const barCols = showScrollbar ? 1 : 0;
1064
+ const textWidth = Math.max(8, contentCols - barCols);
1026
1065
  const allLines = (0, import_react5.useMemo)(() => {
1027
1066
  if (Array.isArray(linesProp)) return linesProp.map((l) => String(l ?? ""));
1028
- return wrap ? wrapTextLines(text, wrapCols) : String(text ?? "").split("\n");
1029
- }, [linesProp, text, wrap, wrapCols]);
1067
+ return wrap ? wrapTextLines(text, textWidth) : String(text ?? "").split("\n");
1068
+ }, [linesProp, text, wrap, textWidth]);
1030
1069
  const needsBar = showScrollbar && allLines.length > viewportRows;
1031
- const textWidth = Math.max(8, termCols - (needsBar ? 1 : 0));
1032
1070
  const maxScroll = Math.max(0, allLines.length - viewportRows);
1033
1071
  const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
1034
1072
  const visible = allLines.slice(clamped, clamped + viewportRows);
@@ -1068,22 +1106,24 @@ function ScrollableText({
1068
1106
  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" : "");
1069
1107
  const rowNodes = visible.map((line, i) => {
1070
1108
  const body = padEndVisible(line, textWidth);
1071
- const glyph = bar ? bar[i] ?? "\u2502" : "";
1109
+ const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
1110
+ const isThumb = glyph === BAR_THUMB;
1072
1111
  return h5(
1073
- import_ink5.Box,
1074
- { key: `L${clamped + i}`, flexDirection: "row" },
1075
- h5(import_ink5.Text, {}, body),
1076
- glyph ? h5(import_ink5.Text, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, glyph) : null
1112
+ import_ink5.Text,
1113
+ { key: `L${clamped + i}` },
1114
+ body,
1115
+ glyph ? h5(import_ink5.Text, { color: isThumb ? "cyan" : "gray" }, glyph) : null
1077
1116
  );
1078
1117
  });
1079
1118
  if (bar && visible.length < viewportRows) {
1080
1119
  for (let i = visible.length; i < viewportRows; i++) {
1120
+ const glyph = bar[i] ?? BAR_TRACK;
1081
1121
  rowNodes.push(
1082
1122
  h5(
1083
- import_ink5.Box,
1084
- { key: `pad${i}`, flexDirection: "row" },
1085
- h5(import_ink5.Text, {}, padEndVisible("", textWidth)),
1086
- h5(import_ink5.Text, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, bar[i] ?? "\u2502")
1123
+ import_ink5.Text,
1124
+ { key: `pad${i}` },
1125
+ padEndVisible("", textWidth),
1126
+ h5(import_ink5.Text, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
1087
1127
  )
1088
1128
  );
1089
1129
  }
@@ -1101,16 +1141,14 @@ var init_scrollable_text = __esm({
1101
1141
  "src/screen/scrollable-text.js"() {
1102
1142
  import_react5 = require("react");
1103
1143
  import_ink5 = require("ink");
1144
+ init_components();
1145
+ init_scrollbar();
1146
+ init_scrollbar();
1104
1147
  h5 = import_react5.createElement;
1105
1148
  SCROLL_KEYS = [
1106
1149
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1107
1150
  { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1108
- { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1109
- { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1110
- { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1111
- { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1112
- { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1113
- { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1151
+ ...PAGE_SCROLL_KEY_BINDINGS
1114
1152
  ];
1115
1153
  }
1116
1154
  });
@@ -1246,6 +1284,8 @@ var init_footer_builder = __esm({
1246
1284
  // src/screen/index.js
1247
1285
  var screen_exports = {};
1248
1286
  __export(screen_exports, {
1287
+ BAR_THUMB: () => BAR_THUMB,
1288
+ BAR_TRACK: () => BAR_TRACK,
1249
1289
  Box: () => import_ink6.Box,
1250
1290
  Divider: () => Divider,
1251
1291
  FooterPresets: () => FooterPresets,
@@ -1255,6 +1295,7 @@ __export(screen_exports, {
1255
1295
  ListItem: () => ListItem,
1256
1296
  MultiColumnListComponent: () => MultiColumnListComponent,
1257
1297
  MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
1298
+ PAGE_SCROLL_KEY_BINDINGS: () => PAGE_SCROLL_KEY_BINDINGS,
1258
1299
  React: () => import_react6.default,
1259
1300
  ScreenBody: () => ScreenBody,
1260
1301
  ScreenContainer: () => ScreenContainer,
@@ -1310,6 +1351,7 @@ var init_screen = __esm({
1310
1351
  init_components();
1311
1352
  init_ui_elements();
1312
1353
  init_scrollable_text();
1354
+ init_scrollbar();
1313
1355
  init_key_bindings();
1314
1356
  init_utils();
1315
1357
  init_footer_builder();
@@ -4162,12 +4204,13 @@ function tasksSchemaSpec(queueName = "tasks") {
4162
4204
  }
4163
4205
  };
4164
4206
  }
4165
- function taskHistoryInsertFromQueueRow(row, overrides) {
4207
+ function taskHistoryInsertFromQueueRow(row, overrides = {}) {
4166
4208
  const { id, ...snapshot } = row;
4167
- void id;
4209
+ const opid = overrides.opid !== void 0 ? overrides.opid : snapshot.opid != null && String(snapshot.opid).trim() !== "" ? snapshot.opid : id;
4168
4210
  return {
4169
4211
  ...snapshot,
4170
- ...overrides
4212
+ ...overrides,
4213
+ opid
4171
4214
  };
4172
4215
  }
4173
4216
  async function ensureTaskTables(context, options = {}) {
@@ -6467,69 +6510,6 @@ var TaskStopRunner = class extends AbstractTask {
6467
6510
  }
6468
6511
  };
6469
6512
 
6470
- // src/tasks/coreTasks/TaskGetLogs.js
6471
- var TaskGetLogs = class extends AbstractTask {
6472
- /**
6473
- * @param {object} context
6474
- * @param {Record<string, unknown>} [overrides]
6475
- * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
6476
- */
6477
- static async resolveCustomParams(context, overrides = {}) {
6478
- const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
6479
- source: "string",
6480
- resource: "string",
6481
- tail: "number default 100",
6482
- afterTs: "string"
6483
- }, overrides);
6484
- const source = typeof merged.source === "string" ? merged.source.trim() : "";
6485
- const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
6486
- if (!source) throw new ParamError('getLogs: param "source" is required');
6487
- if (!resource) throw new ParamError('getLogs: param "resource" is required');
6488
- let tail = Number(merged.tail);
6489
- if (!Number.isFinite(tail) || tail < 1) tail = 100;
6490
- tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
6491
- const out = { source, resource, tail };
6492
- if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
6493
- out.afterTs = merged.afterTs.trim();
6494
- }
6495
- return out;
6496
- }
6497
- /**
6498
- * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
6499
- * @returns {Promise<{ success: boolean, results: unknown }>}
6500
- */
6501
- async run(_reportProgress) {
6502
- const p = this.task.params ?? {};
6503
- const source = String(p.source ?? "").trim();
6504
- const resource = String(p.resource ?? "").trim();
6505
- const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
6506
- const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
6507
- if (!source || !resource) {
6508
- return {
6509
- success: false,
6510
- results: { error: 'getLogs requires params "source" and "resource"' }
6511
- };
6512
- }
6513
- try {
6514
- const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
6515
- source,
6516
- resource,
6517
- tail,
6518
- afterTs
6519
- });
6520
- return {
6521
- success: true,
6522
- results: { records, latestTs, source, resource }
6523
- };
6524
- } catch (e) {
6525
- return {
6526
- success: false,
6527
- results: { error: e?.message ?? String(e) }
6528
- };
6529
- }
6530
- }
6531
- };
6532
-
6533
6513
  // src/tasks/runtimeParams.js
6534
6514
  var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
6535
6515
  var LOGGER_RUNTIME_KEYS = [
@@ -6543,7 +6523,16 @@ var LOGGER_RUNTIME_KEYS = [
6543
6523
  "progressWithTimes",
6544
6524
  "progressThrottleMs"
6545
6525
  ];
6546
- var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
6526
+ var CONTROL_LANE_TASK_NAMES = [
6527
+ "stopRunner",
6528
+ "stop",
6529
+ "pauseRunner",
6530
+ "pause",
6531
+ "unpauseRunner",
6532
+ "unpause",
6533
+ "setRuntimeParam",
6534
+ "setRunnerParam"
6535
+ ];
6547
6536
  function controlLaneTaskNames(extra) {
6548
6537
  const names = [...CONTROL_LANE_TASK_NAMES];
6549
6538
  if (extra == null || extra === "") return names;
@@ -6606,6 +6595,7 @@ function ensureTasksRuntime(context, seed = {}) {
6606
6595
  if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
6607
6596
  if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
6608
6597
  if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
6598
+ if (rt.paused === void 0) rt.paused = seed.paused === true;
6609
6599
  return rt;
6610
6600
  }
6611
6601
  async function applyRuntimeParam(context, key, value) {
@@ -6670,6 +6660,140 @@ function readLoopRuntime(context) {
6670
6660
  };
6671
6661
  }
6672
6662
 
6663
+ // src/tasks/coreTasks/TaskPauseRunner.js
6664
+ async function applyRunnerPaused(context, paused) {
6665
+ const runtime = ensureTasksRuntime(context);
6666
+ const was = runtime.paused === true;
6667
+ runtime.paused = paused === true;
6668
+ const registry = context.servicesRegistry;
6669
+ if (registry && typeof registry === "object") {
6670
+ await updateServicesRegistryMetadata(context, registry, {
6671
+ paused: runtime.paused,
6672
+ pausedAt: runtime.paused ? (/* @__PURE__ */ new Date()).toISOString() : null
6673
+ });
6674
+ }
6675
+ 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).";
6676
+ context.logger?.warn?.(
6677
+ runtime.paused ? `[TaskPauseRunner] ${message}` : `[TaskUnpauseRunner] ${message}`
6678
+ );
6679
+ return {
6680
+ success: true,
6681
+ results: {
6682
+ paused: runtime.paused,
6683
+ message
6684
+ }
6685
+ };
6686
+ }
6687
+ var TaskPauseRunner = class extends AbstractTask {
6688
+ static taskName = "pauseRunner";
6689
+ static description = "Pause a runner: finish in-flight tasks, claim no new worker tasks until unpaused";
6690
+ static aliases = ["pause"];
6691
+ static defaultWaitForResult = true;
6692
+ /**
6693
+ * @param {object} context
6694
+ * @param {Record<string, unknown>} [overrides]
6695
+ * @returns {Promise<object>}
6696
+ */
6697
+ static async resolveParams(context, overrides = {}) {
6698
+ const main = await super.resolveParams(context, overrides);
6699
+ if (!main.serviceName) {
6700
+ throw new ParamError(
6701
+ "pause/pauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
6702
+ );
6703
+ }
6704
+ return main;
6705
+ }
6706
+ async run() {
6707
+ return applyRunnerPaused(this.context, true);
6708
+ }
6709
+ };
6710
+ var TaskUnpauseRunner = class extends AbstractTask {
6711
+ static taskName = "unpauseRunner";
6712
+ static description = "Unpause a runner: resume claiming worker tasks";
6713
+ static aliases = ["unpause"];
6714
+ static defaultWaitForResult = true;
6715
+ /**
6716
+ * @param {object} context
6717
+ * @param {Record<string, unknown>} [overrides]
6718
+ * @returns {Promise<object>}
6719
+ */
6720
+ static async resolveParams(context, overrides = {}) {
6721
+ const main = await super.resolveParams(context, overrides);
6722
+ if (!main.serviceName) {
6723
+ throw new ParamError(
6724
+ "unpause/unpauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
6725
+ );
6726
+ }
6727
+ return main;
6728
+ }
6729
+ async run() {
6730
+ return applyRunnerPaused(this.context, false);
6731
+ }
6732
+ };
6733
+
6734
+ // src/tasks/coreTasks/TaskGetLogs.js
6735
+ var TaskGetLogs = class extends AbstractTask {
6736
+ /**
6737
+ * @param {object} context
6738
+ * @param {Record<string, unknown>} [overrides]
6739
+ * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
6740
+ */
6741
+ static async resolveCustomParams(context, overrides = {}) {
6742
+ const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
6743
+ source: "string",
6744
+ resource: "string",
6745
+ tail: "number default 100",
6746
+ afterTs: "string"
6747
+ }, overrides);
6748
+ const source = typeof merged.source === "string" ? merged.source.trim() : "";
6749
+ const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
6750
+ if (!source) throw new ParamError('getLogs: param "source" is required');
6751
+ if (!resource) throw new ParamError('getLogs: param "resource" is required');
6752
+ let tail = Number(merged.tail);
6753
+ if (!Number.isFinite(tail) || tail < 1) tail = 100;
6754
+ tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
6755
+ const out = { source, resource, tail };
6756
+ if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
6757
+ out.afterTs = merged.afterTs.trim();
6758
+ }
6759
+ return out;
6760
+ }
6761
+ /**
6762
+ * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
6763
+ * @returns {Promise<{ success: boolean, results: unknown }>}
6764
+ */
6765
+ async run(_reportProgress) {
6766
+ const p = this.task.params ?? {};
6767
+ const source = String(p.source ?? "").trim();
6768
+ const resource = String(p.resource ?? "").trim();
6769
+ const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
6770
+ const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
6771
+ if (!source || !resource) {
6772
+ return {
6773
+ success: false,
6774
+ results: { error: 'getLogs requires params "source" and "resource"' }
6775
+ };
6776
+ }
6777
+ try {
6778
+ const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
6779
+ source,
6780
+ resource,
6781
+ tail,
6782
+ afterTs
6783
+ });
6784
+ return {
6785
+ success: true,
6786
+ results: { records, latestTs, source, resource }
6787
+ };
6788
+ } catch (e) {
6789
+ return {
6790
+ success: false,
6791
+ results: { error: e?.message ?? String(e) }
6792
+ };
6793
+ }
6794
+ }
6795
+ };
6796
+
6673
6797
  // src/tasks/coreTasks/TaskSetRuntimeParam.js
6674
6798
  var TaskSetRuntimeParam = class extends AbstractTask {
6675
6799
  static defaultWaitForResult = true;
@@ -6825,7 +6949,7 @@ var TasksRegistry = class _TasksRegistry {
6825
6949
  * @returns {TasksRegistry}
6826
6950
  */
6827
6951
  static withCoreTasks() {
6828
- 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);
6952
+ 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);
6829
6953
  }
6830
6954
  /**
6831
6955
  * Register a single task class under a name. Overwrites any previous entry.
@@ -7187,6 +7311,7 @@ async function runTasksLoop(context, options) {
7187
7311
  const defaultMeta = {
7188
7312
  component: "tasks-runner",
7189
7313
  allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
7314
+ paused: false,
7190
7315
  runtime: {
7191
7316
  maxParallel: loop0.maxParallel,
7192
7317
  pollMs: loop0.pollMs,
@@ -7253,28 +7378,38 @@ async function runTasksLoop(context, options) {
7253
7378
  if (claimJitterMs > 0) {
7254
7379
  await sleepMs(Math.floor(Math.random() * (claimJitterMs + 1)));
7255
7380
  }
7256
- while (runningPromises.size < maxParallel) {
7257
- const claimed = await claimNextRunnableTask(
7258
- context,
7259
- tasksTable,
7260
- target,
7261
- registry,
7262
- scanLimit,
7263
- allowedTasks,
7264
- runnerIdentity
7265
- );
7266
- if (!claimed) break;
7267
- const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
7268
- if (outcome.stopRunnerRequested && !stopRequested) {
7269
- stopRequested = true;
7270
- stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
7271
- context.tasksRunnerStop = true;
7272
- await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
7273
- }
7274
- }).finally(() => {
7275
- runningPromises.delete(p);
7276
- });
7277
- runningPromises.add(p);
7381
+ const paused = context.tasksRuntime?.paused === true;
7382
+ if (!paused) {
7383
+ while (runningPromises.size < maxParallel) {
7384
+ const claimed = await claimNextRunnableTask(
7385
+ context,
7386
+ tasksTable,
7387
+ target,
7388
+ registry,
7389
+ scanLimit,
7390
+ allowedTasks,
7391
+ runnerIdentity
7392
+ );
7393
+ if (!claimed) break;
7394
+ const p = executeClaimedTask(
7395
+ context,
7396
+ tasksTable,
7397
+ historyTable,
7398
+ claimed,
7399
+ registry,
7400
+ runningTaskInstances
7401
+ ).then(async (outcome) => {
7402
+ if (outcome.stopRunnerRequested && !stopRequested) {
7403
+ stopRequested = true;
7404
+ stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
7405
+ context.tasksRunnerStop = true;
7406
+ await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
7407
+ }
7408
+ }).finally(() => {
7409
+ runningPromises.delete(p);
7410
+ });
7411
+ runningPromises.add(p);
7412
+ }
7278
7413
  }
7279
7414
  const wakePromises = [...runningPromises];
7280
7415
  if (runningControlPromise) {