@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.
@@ -94,9 +94,48 @@ var init_components = __esm({
94
94
  }
95
95
  });
96
96
 
97
+ // src/screen/scrollbar.js
98
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
99
+ const view = Math.max(1, Math.floor(viewportRows));
100
+ const total = Math.max(0, Math.floor(totalLines));
101
+ if (total <= view) return null;
102
+ const maxScroll = total - view;
103
+ const thumbSize = Math.max(1, Math.round(view / total * view));
104
+ const travel = Math.max(0, view - thumbSize);
105
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
106
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
107
+ const glyphs = [];
108
+ for (let i = 0; i < view; i++) {
109
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
110
+ }
111
+ return glyphs;
112
+ }
113
+ var BAR_THUMB, BAR_TRACK, PAGE_SCROLL_KEY_BINDINGS;
114
+ var init_scrollbar = __esm({
115
+ "src/screen/scrollbar.js"() {
116
+ BAR_THUMB = "#";
117
+ BAR_TRACK = "|";
118
+ PAGE_SCROLL_KEY_BINDINGS = [
119
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
120
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
121
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
122
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
123
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
124
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
125
+ ];
126
+ }
127
+ });
128
+
97
129
  // src/screen/list-components.js
98
130
  import React, { useState, useEffect, useRef, createElement } from "react";
99
131
  import { Box as Box2, Text as Text2 } from "ink";
132
+ function clipNameForScrollbar(name, needsBar) {
133
+ if (!needsBar || name == null) return name;
134
+ const s = String(name);
135
+ if (s.length <= 1) return s;
136
+ if (/\s$/.test(s)) return s.slice(0, -1);
137
+ return s;
138
+ }
100
139
  function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
101
140
  const [, forceUpdate] = useState({});
102
141
  const termWidth = (process.stdout.columns || 80) - 8;
@@ -310,17 +349,42 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
310
349
  });
311
350
  ctx.setAction("scrollUp", () => {
312
351
  const { scrollOffset: currentScrollOffset } = scrollStateRef.current;
313
- const newScrollOffset = Math.max(0, currentScrollOffset - 1);
314
- setScrollOffset(newScrollOffset);
352
+ setScrollOffset(Math.max(0, currentScrollOffset - 1));
315
353
  forceUpdate({});
316
354
  });
317
355
  ctx.setAction("scrollDown", () => {
318
356
  const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
319
357
  const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
320
- const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
321
- setScrollOffset(newScrollOffset);
358
+ setScrollOffset(Math.min(currentMaxScrollOffset, currentScrollOffset + 1));
359
+ forceUpdate({});
360
+ });
361
+ ctx.setAction("pageUp", () => {
362
+ const { maxHeight: vh } = scrollStateRef.current;
363
+ const page = Math.max(1, vh || 1);
364
+ const newIndex = Math.max(0, selectedIndexRef.current - page);
365
+ selectedIndexRef.current = newIndex;
366
+ const list = displayItemsRef.current;
367
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
368
+ setScrollOffset(newIndex);
322
369
  forceUpdate({});
323
370
  });
371
+ ctx.setAction("pageDown", () => {
372
+ const { maxHeight: vh, totalItems } = scrollStateRef.current;
373
+ const page = Math.max(1, vh || 1);
374
+ const maxIndex = Math.max(0, totalItems - 1);
375
+ const newIndex = Math.min(maxIndex, selectedIndexRef.current + page);
376
+ selectedIndexRef.current = newIndex;
377
+ const list = displayItemsRef.current;
378
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
379
+ const maxScroll = Math.max(0, totalItems - page);
380
+ setScrollOffset(Math.min(maxScroll, Math.max(0, newIndex - page + 1)));
381
+ forceUpdate({});
382
+ });
383
+ const navBindings = [
384
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
385
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
386
+ ...PAGE_SCROLL_KEY_BINDINGS
387
+ ];
324
388
  if (sortable) {
325
389
  ctx.setAction("toggleSort", () => {
326
390
  const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
@@ -367,8 +431,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
367
431
  }
368
432
  };
369
433
  ctx.setKeyBinding([
370
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
371
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
434
+ ...navBindings,
372
435
  {
373
436
  key: "s",
374
437
  caption: sortCaption,
@@ -378,47 +441,47 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
378
441
  ]);
379
442
  ctx.update();
380
443
  } else {
381
- ctx.setKeyBinding([
382
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
383
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
384
- ]);
444
+ ctx.setKeyBinding(navBindings);
385
445
  }
386
446
  }, [sortOrder, sortable]);
387
447
  const selectedIndex = selectedIndexRef.current;
448
+ const needsBar = displayItems.length > effectiveMaxHeight;
449
+ const barGlyphs = needsBar ? scrollbarGlyphs(effectiveMaxHeight, displayItems.length, clampedScrollOffset) : null;
450
+ const appendBar = (rowContent, displayIndex) => {
451
+ if (!barGlyphs) return rowContent;
452
+ const glyph = barGlyphs[displayIndex] ?? BAR_TRACK;
453
+ return h2(
454
+ Box2,
455
+ { flexDirection: "row" },
456
+ rowContent,
457
+ h2(Text2, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
458
+ );
459
+ };
388
460
  const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
389
461
  const isFirstVisible = displayIndex === 0;
390
462
  const isLastVisible = displayIndex === visibleItems.length - 1;
391
- let arrowPrefix = "";
392
- let selectionPrefix = "";
393
- if (isFirstVisible && canScrollUp) {
394
- arrowPrefix = "\u2191 ";
395
- } else if (isLastVisible && canScrollDown) {
396
- arrowPrefix = "\u2193 ";
397
- } else {
398
- arrowPrefix = " ";
399
- }
400
- if (isSelected) {
401
- selectionPrefix = selectionMarker;
402
- } else {
403
- selectionPrefix = " ".repeat(selectionMarker.length);
463
+ let arrowPrefix = " ";
464
+ if (!needsBar) {
465
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
466
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
404
467
  }
468
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
469
+ const label = clipNameForScrollbar(item.name, needsBar);
405
470
  return h2(
406
471
  Box2,
407
472
  { flexDirection: "row" },
408
- // Arrow (clickable if functional, not highlighted)
409
- h2(Text2, {
410
- key: `arrow-${actualIndex}`,
411
- color: "white"
412
- }, arrowPrefix),
413
- // Selection marker space (always same width, not highlighted)
473
+ h2(Text2, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
414
474
  h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
415
- // Item name (highlighted if selected)
416
- h2(Text2, {
417
- key: `name-${actualIndex}`,
418
- color: isSelected ? "black" : "white",
419
- backgroundColor: isSelected ? "cyan" : void 0,
420
- bold: isSelected
421
- }, item.name)
475
+ h2(
476
+ Text2,
477
+ {
478
+ key: `name-${actualIndex}`,
479
+ color: isSelected ? "black" : "white",
480
+ backgroundColor: isSelected ? "cyan" : void 0,
481
+ bold: isSelected
482
+ },
483
+ label
484
+ )
422
485
  );
423
486
  };
424
487
  const itemRenderer = renderItem || defaultRenderItem;
@@ -431,42 +494,32 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
431
494
  if (renderItem) {
432
495
  const isFirstVisible = displayIndex === 0;
433
496
  const isLastVisible = displayIndex === visibleItems.length - 1;
434
- let arrowPrefix = "";
435
- let selectionPrefix = "";
436
- if (isFirstVisible && canScrollUp) {
437
- arrowPrefix = "\u2191 ";
438
- } else if (isLastVisible && canScrollDown) {
439
- arrowPrefix = "\u2193 ";
440
- } else {
441
- arrowPrefix = " ";
442
- }
443
- if (isSelected) {
444
- selectionPrefix = selectionMarker;
445
- } else {
446
- selectionPrefix = " ".repeat(selectionMarker.length);
497
+ let arrowPrefix = " ";
498
+ if (!needsBar) {
499
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
500
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
447
501
  }
502
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
503
+ const clipped = needsBar ? { ...item, name: clipNameForScrollbar(item.name, true) } : item;
504
+ const row = h2(
505
+ Box2,
506
+ { flexDirection: "row" },
507
+ h2(Text2, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
508
+ h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
509
+ renderItem(clipped, isSelected, displayIndex)
510
+ );
448
511
  return h2(ScreenRow, {
449
512
  key: `item-${actualIndex}`,
450
- children: h2(
451
- Box2,
452
- { flexDirection: "row" },
453
- // Arrow (clickable if functional, not highlighted)
454
- h2(Text2, {
455
- key: `arrow-${actualIndex}`,
456
- color: "white"
457
- }, arrowPrefix),
458
- // Selection marker space (always same width, not highlighted)
459
- h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
460
- // Custom rendered content
461
- renderItem(item, isSelected, displayIndex)
462
- )
463
- });
464
- } else {
465
- return h2(ScreenRow, {
466
- key: `item-${actualIndex}`,
467
- children: itemRenderer(item, isSelected, displayIndex, actualIndex)
513
+ children: appendBar(row, displayIndex)
468
514
  });
469
515
  }
516
+ return h2(ScreenRow, {
517
+ key: `item-${actualIndex}`,
518
+ children: appendBar(
519
+ itemRenderer(item, isSelected, displayIndex, actualIndex),
520
+ displayIndex
521
+ )
522
+ });
470
523
  })
471
524
  );
472
525
  }
@@ -474,6 +527,7 @@ var h2;
474
527
  var init_list_components = __esm({
475
528
  "src/screen/list-components.js"() {
476
529
  init_components();
530
+ init_scrollbar();
477
531
  h2 = createElement;
478
532
  }
479
533
  });
@@ -968,21 +1022,6 @@ function wrapTextLines(text, cols) {
968
1022
  }
969
1023
  return out;
970
1024
  }
971
- function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
972
- const view = Math.max(1, Math.floor(viewportRows));
973
- const total = Math.max(0, Math.floor(totalLines));
974
- if (total <= view) return null;
975
- const maxScroll = total - view;
976
- const thumbSize = Math.max(1, Math.round(view / total * view));
977
- const travel = Math.max(0, view - thumbSize);
978
- const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
979
- const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
980
- const glyphs = [];
981
- for (let i = 0; i < view; i++) {
982
- glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? "\u2588" : "\u2502");
983
- }
984
- return glyphs;
985
- }
986
1025
  function padEndVisible(s, w) {
987
1026
  const t = String(s ?? "");
988
1027
  if (t.length >= w) return t.slice(0, w);
@@ -1001,20 +1040,19 @@ function ScrollableText({
1001
1040
  }) {
1002
1041
  const [scrollTop, setScrollTop] = useState3(0);
1003
1042
  const [, bump] = useState3(0);
1004
- const termCols = process.stdout.columns || 80;
1005
1043
  const termRows = process.stdout.rows || 24;
1006
1044
  const viewportRows = Math.max(
1007
1045
  4,
1008
1046
  maxHeight != null ? Math.floor(maxHeight) : Math.max(8, termRows - 8)
1009
1047
  );
1010
- const provisionalBar = showScrollbar ? 1 : 0;
1011
- const wrapCols = Math.max(8, termCols - provisionalBar);
1048
+ const contentCols = Math.max(20, getScreenWidth() - 4);
1049
+ const barCols = showScrollbar ? 1 : 0;
1050
+ const textWidth = Math.max(8, contentCols - barCols);
1012
1051
  const allLines = useMemo(() => {
1013
1052
  if (Array.isArray(linesProp)) return linesProp.map((l) => String(l ?? ""));
1014
- return wrap ? wrapTextLines(text, wrapCols) : String(text ?? "").split("\n");
1015
- }, [linesProp, text, wrap, wrapCols]);
1053
+ return wrap ? wrapTextLines(text, textWidth) : String(text ?? "").split("\n");
1054
+ }, [linesProp, text, wrap, textWidth]);
1016
1055
  const needsBar = showScrollbar && allLines.length > viewportRows;
1017
- const textWidth = Math.max(8, termCols - (needsBar ? 1 : 0));
1018
1056
  const maxScroll = Math.max(0, allLines.length - viewportRows);
1019
1057
  const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
1020
1058
  const visible = allLines.slice(clamped, clamped + viewportRows);
@@ -1054,22 +1092,24 @@ function ScrollableText({
1054
1092
  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" : "");
1055
1093
  const rowNodes = visible.map((line, i) => {
1056
1094
  const body = padEndVisible(line, textWidth);
1057
- const glyph = bar ? bar[i] ?? "\u2502" : "";
1095
+ const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
1096
+ const isThumb = glyph === BAR_THUMB;
1058
1097
  return h5(
1059
- Box4,
1060
- { key: `L${clamped + i}`, flexDirection: "row" },
1061
- h5(Text5, {}, body),
1062
- glyph ? h5(Text5, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, glyph) : null
1098
+ Text5,
1099
+ { key: `L${clamped + i}` },
1100
+ body,
1101
+ glyph ? h5(Text5, { color: isThumb ? "cyan" : "gray" }, glyph) : null
1063
1102
  );
1064
1103
  });
1065
1104
  if (bar && visible.length < viewportRows) {
1066
1105
  for (let i = visible.length; i < viewportRows; i++) {
1106
+ const glyph = bar[i] ?? BAR_TRACK;
1067
1107
  rowNodes.push(
1068
1108
  h5(
1069
- Box4,
1070
- { key: `pad${i}`, flexDirection: "row" },
1071
- h5(Text5, {}, padEndVisible("", textWidth)),
1072
- h5(Text5, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, bar[i] ?? "\u2502")
1109
+ Text5,
1110
+ { key: `pad${i}` },
1111
+ padEndVisible("", textWidth),
1112
+ h5(Text5, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
1073
1113
  )
1074
1114
  );
1075
1115
  }
@@ -1085,16 +1125,14 @@ function ScrollableText({
1085
1125
  var h5, SCROLL_KEYS;
1086
1126
  var init_scrollable_text = __esm({
1087
1127
  "src/screen/scrollable-text.js"() {
1128
+ init_components();
1129
+ init_scrollbar();
1130
+ init_scrollbar();
1088
1131
  h5 = createElement2;
1089
1132
  SCROLL_KEYS = [
1090
1133
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1091
1134
  { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1092
- { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1093
- { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1094
- { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1095
- { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1096
- { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1097
- { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1135
+ ...PAGE_SCROLL_KEY_BINDINGS
1098
1136
  ];
1099
1137
  }
1100
1138
  });
@@ -1230,6 +1268,8 @@ var init_footer_builder = __esm({
1230
1268
  // src/screen/index.js
1231
1269
  var screen_exports = {};
1232
1270
  __export(screen_exports, {
1271
+ BAR_THUMB: () => BAR_THUMB,
1272
+ BAR_TRACK: () => BAR_TRACK,
1233
1273
  Box: () => Box5,
1234
1274
  Divider: () => Divider,
1235
1275
  FooterPresets: () => FooterPresets,
@@ -1239,6 +1279,7 @@ __export(screen_exports, {
1239
1279
  ListItem: () => ListItem,
1240
1280
  MultiColumnListComponent: () => MultiColumnListComponent,
1241
1281
  MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
1282
+ PAGE_SCROLL_KEY_BINDINGS: () => PAGE_SCROLL_KEY_BINDINGS,
1242
1283
  React: () => React2,
1243
1284
  ScreenBody: () => ScreenBody,
1244
1285
  ScreenContainer: () => ScreenContainer,
@@ -1294,6 +1335,7 @@ var init_screen = __esm({
1294
1335
  init_components();
1295
1336
  init_ui_elements();
1296
1337
  init_scrollable_text();
1338
+ init_scrollbar();
1297
1339
  init_key_bindings();
1298
1340
  init_utils();
1299
1341
  init_footer_builder();
@@ -4146,12 +4188,13 @@ function tasksSchemaSpec(queueName = "tasks") {
4146
4188
  }
4147
4189
  };
4148
4190
  }
4149
- function taskHistoryInsertFromQueueRow(row, overrides) {
4191
+ function taskHistoryInsertFromQueueRow(row, overrides = {}) {
4150
4192
  const { id, ...snapshot } = row;
4151
- void id;
4193
+ const opid = overrides.opid !== void 0 ? overrides.opid : snapshot.opid != null && String(snapshot.opid).trim() !== "" ? snapshot.opid : id;
4152
4194
  return {
4153
4195
  ...snapshot,
4154
- ...overrides
4196
+ ...overrides,
4197
+ opid
4155
4198
  };
4156
4199
  }
4157
4200
  async function ensureTaskTables(context, options = {}) {
@@ -6451,69 +6494,6 @@ var TaskStopRunner = class extends AbstractTask {
6451
6494
  }
6452
6495
  };
6453
6496
 
6454
- // src/tasks/coreTasks/TaskGetLogs.js
6455
- var TaskGetLogs = class extends AbstractTask {
6456
- /**
6457
- * @param {object} context
6458
- * @param {Record<string, unknown>} [overrides]
6459
- * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
6460
- */
6461
- static async resolveCustomParams(context, overrides = {}) {
6462
- const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
6463
- source: "string",
6464
- resource: "string",
6465
- tail: "number default 100",
6466
- afterTs: "string"
6467
- }, overrides);
6468
- const source = typeof merged.source === "string" ? merged.source.trim() : "";
6469
- const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
6470
- if (!source) throw new ParamError('getLogs: param "source" is required');
6471
- if (!resource) throw new ParamError('getLogs: param "resource" is required');
6472
- let tail = Number(merged.tail);
6473
- if (!Number.isFinite(tail) || tail < 1) tail = 100;
6474
- tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
6475
- const out = { source, resource, tail };
6476
- if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
6477
- out.afterTs = merged.afterTs.trim();
6478
- }
6479
- return out;
6480
- }
6481
- /**
6482
- * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
6483
- * @returns {Promise<{ success: boolean, results: unknown }>}
6484
- */
6485
- async run(_reportProgress) {
6486
- const p = this.task.params ?? {};
6487
- const source = String(p.source ?? "").trim();
6488
- const resource = String(p.resource ?? "").trim();
6489
- const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
6490
- const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
6491
- if (!source || !resource) {
6492
- return {
6493
- success: false,
6494
- results: { error: 'getLogs requires params "source" and "resource"' }
6495
- };
6496
- }
6497
- try {
6498
- const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
6499
- source,
6500
- resource,
6501
- tail,
6502
- afterTs
6503
- });
6504
- return {
6505
- success: true,
6506
- results: { records, latestTs, source, resource }
6507
- };
6508
- } catch (e) {
6509
- return {
6510
- success: false,
6511
- results: { error: e?.message ?? String(e) }
6512
- };
6513
- }
6514
- }
6515
- };
6516
-
6517
6497
  // src/tasks/runtimeParams.js
6518
6498
  var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
6519
6499
  var LOGGER_RUNTIME_KEYS = [
@@ -6527,7 +6507,16 @@ var LOGGER_RUNTIME_KEYS = [
6527
6507
  "progressWithTimes",
6528
6508
  "progressThrottleMs"
6529
6509
  ];
6530
- var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
6510
+ var CONTROL_LANE_TASK_NAMES = [
6511
+ "stopRunner",
6512
+ "stop",
6513
+ "pauseRunner",
6514
+ "pause",
6515
+ "unpauseRunner",
6516
+ "unpause",
6517
+ "setRuntimeParam",
6518
+ "setRunnerParam"
6519
+ ];
6531
6520
  function controlLaneTaskNames(extra) {
6532
6521
  const names = [...CONTROL_LANE_TASK_NAMES];
6533
6522
  if (extra == null || extra === "") return names;
@@ -6590,6 +6579,7 @@ function ensureTasksRuntime(context, seed = {}) {
6590
6579
  if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
6591
6580
  if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
6592
6581
  if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
6582
+ if (rt.paused === void 0) rt.paused = seed.paused === true;
6593
6583
  return rt;
6594
6584
  }
6595
6585
  async function applyRuntimeParam(context, key, value) {
@@ -6654,6 +6644,140 @@ function readLoopRuntime(context) {
6654
6644
  };
6655
6645
  }
6656
6646
 
6647
+ // src/tasks/coreTasks/TaskPauseRunner.js
6648
+ async function applyRunnerPaused(context, paused) {
6649
+ const runtime = ensureTasksRuntime(context);
6650
+ const was = runtime.paused === true;
6651
+ runtime.paused = paused === true;
6652
+ const registry = context.servicesRegistry;
6653
+ if (registry && typeof registry === "object") {
6654
+ await updateServicesRegistryMetadata(context, registry, {
6655
+ paused: runtime.paused,
6656
+ pausedAt: runtime.paused ? (/* @__PURE__ */ new Date()).toISOString() : null
6657
+ });
6658
+ }
6659
+ 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).";
6660
+ context.logger?.warn?.(
6661
+ runtime.paused ? `[TaskPauseRunner] ${message}` : `[TaskUnpauseRunner] ${message}`
6662
+ );
6663
+ return {
6664
+ success: true,
6665
+ results: {
6666
+ paused: runtime.paused,
6667
+ message
6668
+ }
6669
+ };
6670
+ }
6671
+ var TaskPauseRunner = class extends AbstractTask {
6672
+ static taskName = "pauseRunner";
6673
+ static description = "Pause a runner: finish in-flight tasks, claim no new worker tasks until unpaused";
6674
+ static aliases = ["pause"];
6675
+ static defaultWaitForResult = true;
6676
+ /**
6677
+ * @param {object} context
6678
+ * @param {Record<string, unknown>} [overrides]
6679
+ * @returns {Promise<object>}
6680
+ */
6681
+ static async resolveParams(context, overrides = {}) {
6682
+ const main = await super.resolveParams(context, overrides);
6683
+ if (!main.serviceName) {
6684
+ throw new ParamError(
6685
+ "pause/pauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
6686
+ );
6687
+ }
6688
+ return main;
6689
+ }
6690
+ async run() {
6691
+ return applyRunnerPaused(this.context, true);
6692
+ }
6693
+ };
6694
+ var TaskUnpauseRunner = class extends AbstractTask {
6695
+ static taskName = "unpauseRunner";
6696
+ static description = "Unpause a runner: resume claiming worker tasks";
6697
+ static aliases = ["unpause"];
6698
+ static defaultWaitForResult = true;
6699
+ /**
6700
+ * @param {object} context
6701
+ * @param {Record<string, unknown>} [overrides]
6702
+ * @returns {Promise<object>}
6703
+ */
6704
+ static async resolveParams(context, overrides = {}) {
6705
+ const main = await super.resolveParams(context, overrides);
6706
+ if (!main.serviceName) {
6707
+ throw new ParamError(
6708
+ "unpause/unpauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
6709
+ );
6710
+ }
6711
+ return main;
6712
+ }
6713
+ async run() {
6714
+ return applyRunnerPaused(this.context, false);
6715
+ }
6716
+ };
6717
+
6718
+ // src/tasks/coreTasks/TaskGetLogs.js
6719
+ var TaskGetLogs = class extends AbstractTask {
6720
+ /**
6721
+ * @param {object} context
6722
+ * @param {Record<string, unknown>} [overrides]
6723
+ * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
6724
+ */
6725
+ static async resolveCustomParams(context, overrides = {}) {
6726
+ const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
6727
+ source: "string",
6728
+ resource: "string",
6729
+ tail: "number default 100",
6730
+ afterTs: "string"
6731
+ }, overrides);
6732
+ const source = typeof merged.source === "string" ? merged.source.trim() : "";
6733
+ const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
6734
+ if (!source) throw new ParamError('getLogs: param "source" is required');
6735
+ if (!resource) throw new ParamError('getLogs: param "resource" is required');
6736
+ let tail = Number(merged.tail);
6737
+ if (!Number.isFinite(tail) || tail < 1) tail = 100;
6738
+ tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
6739
+ const out = { source, resource, tail };
6740
+ if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
6741
+ out.afterTs = merged.afterTs.trim();
6742
+ }
6743
+ return out;
6744
+ }
6745
+ /**
6746
+ * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
6747
+ * @returns {Promise<{ success: boolean, results: unknown }>}
6748
+ */
6749
+ async run(_reportProgress) {
6750
+ const p = this.task.params ?? {};
6751
+ const source = String(p.source ?? "").trim();
6752
+ const resource = String(p.resource ?? "").trim();
6753
+ const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
6754
+ const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
6755
+ if (!source || !resource) {
6756
+ return {
6757
+ success: false,
6758
+ results: { error: 'getLogs requires params "source" and "resource"' }
6759
+ };
6760
+ }
6761
+ try {
6762
+ const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
6763
+ source,
6764
+ resource,
6765
+ tail,
6766
+ afterTs
6767
+ });
6768
+ return {
6769
+ success: true,
6770
+ results: { records, latestTs, source, resource }
6771
+ };
6772
+ } catch (e) {
6773
+ return {
6774
+ success: false,
6775
+ results: { error: e?.message ?? String(e) }
6776
+ };
6777
+ }
6778
+ }
6779
+ };
6780
+
6657
6781
  // src/tasks/coreTasks/TaskSetRuntimeParam.js
6658
6782
  var TaskSetRuntimeParam = class extends AbstractTask {
6659
6783
  static defaultWaitForResult = true;
@@ -6809,7 +6933,7 @@ var TasksRegistry = class _TasksRegistry {
6809
6933
  * @returns {TasksRegistry}
6810
6934
  */
6811
6935
  static withCoreTasks() {
6812
- 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);
6936
+ 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);
6813
6937
  }
6814
6938
  /**
6815
6939
  * Register a single task class under a name. Overwrites any previous entry.
@@ -7171,6 +7295,7 @@ async function runTasksLoop(context, options) {
7171
7295
  const defaultMeta = {
7172
7296
  component: "tasks-runner",
7173
7297
  allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
7298
+ paused: false,
7174
7299
  runtime: {
7175
7300
  maxParallel: loop0.maxParallel,
7176
7301
  pollMs: loop0.pollMs,
@@ -7237,28 +7362,38 @@ async function runTasksLoop(context, options) {
7237
7362
  if (claimJitterMs > 0) {
7238
7363
  await sleepMs(Math.floor(Math.random() * (claimJitterMs + 1)));
7239
7364
  }
7240
- while (runningPromises.size < maxParallel) {
7241
- const claimed = await claimNextRunnableTask(
7242
- context,
7243
- tasksTable,
7244
- target,
7245
- registry,
7246
- scanLimit,
7247
- allowedTasks,
7248
- runnerIdentity
7249
- );
7250
- if (!claimed) break;
7251
- const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
7252
- if (outcome.stopRunnerRequested && !stopRequested) {
7253
- stopRequested = true;
7254
- stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
7255
- context.tasksRunnerStop = true;
7256
- await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
7257
- }
7258
- }).finally(() => {
7259
- runningPromises.delete(p);
7260
- });
7261
- runningPromises.add(p);
7365
+ const paused = context.tasksRuntime?.paused === true;
7366
+ if (!paused) {
7367
+ while (runningPromises.size < maxParallel) {
7368
+ const claimed = await claimNextRunnableTask(
7369
+ context,
7370
+ tasksTable,
7371
+ target,
7372
+ registry,
7373
+ scanLimit,
7374
+ allowedTasks,
7375
+ runnerIdentity
7376
+ );
7377
+ if (!claimed) break;
7378
+ const p = executeClaimedTask(
7379
+ context,
7380
+ tasksTable,
7381
+ historyTable,
7382
+ claimed,
7383
+ registry,
7384
+ runningTaskInstances
7385
+ ).then(async (outcome) => {
7386
+ if (outcome.stopRunnerRequested && !stopRequested) {
7387
+ stopRequested = true;
7388
+ stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
7389
+ context.tasksRunnerStop = true;
7390
+ await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
7391
+ }
7392
+ }).finally(() => {
7393
+ runningPromises.delete(p);
7394
+ });
7395
+ runningPromises.add(p);
7396
+ }
7262
7397
  }
7263
7398
  const wakePromises = [...runningPromises];
7264
7399
  if (runningControlPromise) {