@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.cjs CHANGED
@@ -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);
382
+ forceUpdate({});
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)));
335
394
  forceUpdate({});
336
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 = " ";
510
+ let arrowPrefix = " ";
511
+ if (!needsBar) {
512
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
513
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
455
514
  }
456
- if (isSelected) {
457
- selectionPrefix = selectionMarker;
458
- } else {
459
- selectionPrefix = " ".repeat(selectionMarker.length);
460
- }
461
- return h2(ScreenRow, {
462
- 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 {
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
+ );
478
524
  return h2(ScreenRow, {
479
525
  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
  });
@@ -1260,6 +1298,7 @@ var init_screen = __esm({
1260
1298
  init_components();
1261
1299
  init_ui_elements();
1262
1300
  init_scrollable_text();
1301
+ init_scrollbar();
1263
1302
  init_key_bindings();
1264
1303
  init_utils();
1265
1304
  init_footer_builder();
@@ -1277,6 +1316,8 @@ __export(src_exports, {
1277
1316
  AbstractTask: () => AbstractTask,
1278
1317
  Args: () => Args,
1279
1318
  Aws: () => Aws,
1319
+ BAR_THUMB: () => BAR_THUMB,
1320
+ BAR_TRACK: () => BAR_TRACK,
1280
1321
  Box: () => import_ink6.Box,
1281
1322
  Db: () => Db,
1282
1323
  Divider: () => Divider,
@@ -1291,6 +1332,7 @@ __export(src_exports, {
1291
1332
  ListItem: () => ListItem,
1292
1333
  MultiColumnListComponent: () => MultiColumnListComponent,
1293
1334
  MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
1335
+ PAGE_SCROLL_KEY_BINDINGS: () => PAGE_SCROLL_KEY_BINDINGS,
1294
1336
  Params: () => Params,
1295
1337
  REMOTE_CLI_REL: () => REMOTE_CLI_REL,
1296
1338
  React: () => import_react6.default,
@@ -1304,6 +1346,7 @@ __export(src_exports, {
1304
1346
  ScreenTitle: () => ScreenTitle,
1305
1347
  ScrollableText: () => ScrollableText,
1306
1348
  TaskGetLogs: () => TaskGetLogs,
1349
+ TaskPauseRunner: () => TaskPauseRunner,
1307
1350
  TaskPing: () => TaskPing,
1308
1351
  TaskSampleProcess: () => TaskSampleProcess,
1309
1352
  TaskSetRuntimeParam: () => TaskSetRuntimeParam,
@@ -1311,6 +1354,7 @@ __export(src_exports, {
1311
1354
  TaskStopRunner: () => TaskStopRunner,
1312
1355
  TaskSumAB: () => TaskSumAB,
1313
1356
  TaskSystemInfo: () => TaskSystemInfo,
1357
+ TaskUnpauseRunner: () => TaskUnpauseRunner,
1314
1358
  TasksManager: () => TasksManager,
1315
1359
  TasksRegistry: () => TasksRegistry,
1316
1360
  Text: () => import_ink6.Text,
@@ -1318,6 +1362,7 @@ __export(src_exports, {
1318
1362
  activateRelease: () => activateRelease,
1319
1363
  appendDeployLog: () => appendDeployLog,
1320
1364
  appendTaskIpcLog: () => appendTaskIpcLog,
1365
+ applyRunnerPaused: () => applyRunnerPaused,
1321
1366
  applyRuntimeParam: () => applyRuntimeParam,
1322
1367
  applyRuntimePatch: () => applyRuntimePatch,
1323
1368
  bindingIdentity: () => bindingIdentity,
@@ -7185,12 +7230,13 @@ function tasksSchemaSpec(queueName = "tasks") {
7185
7230
  }
7186
7231
  };
7187
7232
  }
7188
- function taskHistoryInsertFromQueueRow(row, overrides) {
7233
+ function taskHistoryInsertFromQueueRow(row, overrides = {}) {
7189
7234
  const { id, ...snapshot } = row;
7190
- void id;
7235
+ const opid = overrides.opid !== void 0 ? overrides.opid : snapshot.opid != null && String(snapshot.opid).trim() !== "" ? snapshot.opid : id;
7191
7236
  return {
7192
7237
  ...snapshot,
7193
- ...overrides
7238
+ ...overrides,
7239
+ opid
7194
7240
  };
7195
7241
  }
7196
7242
  async function ensureTaskTables(context, options = {}) {
@@ -8453,69 +8499,6 @@ var TaskStopRunner = class extends AbstractTask {
8453
8499
  }
8454
8500
  };
8455
8501
 
8456
- // src/tasks/coreTasks/TaskGetLogs.js
8457
- var TaskGetLogs = class extends AbstractTask {
8458
- /**
8459
- * @param {object} context
8460
- * @param {Record<string, unknown>} [overrides]
8461
- * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
8462
- */
8463
- static async resolveCustomParams(context, overrides = {}) {
8464
- const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
8465
- source: "string",
8466
- resource: "string",
8467
- tail: "number default 100",
8468
- afterTs: "string"
8469
- }, overrides);
8470
- const source = typeof merged.source === "string" ? merged.source.trim() : "";
8471
- const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
8472
- if (!source) throw new ParamError('getLogs: param "source" is required');
8473
- if (!resource) throw new ParamError('getLogs: param "resource" is required');
8474
- let tail = Number(merged.tail);
8475
- if (!Number.isFinite(tail) || tail < 1) tail = 100;
8476
- tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
8477
- const out = { source, resource, tail };
8478
- if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
8479
- out.afterTs = merged.afterTs.trim();
8480
- }
8481
- return out;
8482
- }
8483
- /**
8484
- * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
8485
- * @returns {Promise<{ success: boolean, results: unknown }>}
8486
- */
8487
- async run(_reportProgress) {
8488
- const p = this.task.params ?? {};
8489
- const source = String(p.source ?? "").trim();
8490
- const resource = String(p.resource ?? "").trim();
8491
- const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
8492
- const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
8493
- if (!source || !resource) {
8494
- return {
8495
- success: false,
8496
- results: { error: 'getLogs requires params "source" and "resource"' }
8497
- };
8498
- }
8499
- try {
8500
- const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
8501
- source,
8502
- resource,
8503
- tail,
8504
- afterTs
8505
- });
8506
- return {
8507
- success: true,
8508
- results: { records, latestTs, source, resource }
8509
- };
8510
- } catch (e) {
8511
- return {
8512
- success: false,
8513
- results: { error: e?.message ?? String(e) }
8514
- };
8515
- }
8516
- }
8517
- };
8518
-
8519
8502
  // src/tasks/runtimeParams.js
8520
8503
  var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
8521
8504
  var LOGGER_RUNTIME_KEYS = [
@@ -8529,7 +8512,16 @@ var LOGGER_RUNTIME_KEYS = [
8529
8512
  "progressWithTimes",
8530
8513
  "progressThrottleMs"
8531
8514
  ];
8532
- var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
8515
+ var CONTROL_LANE_TASK_NAMES = [
8516
+ "stopRunner",
8517
+ "stop",
8518
+ "pauseRunner",
8519
+ "pause",
8520
+ "unpauseRunner",
8521
+ "unpause",
8522
+ "setRuntimeParam",
8523
+ "setRunnerParam"
8524
+ ];
8533
8525
  function controlLaneTaskNames(extra) {
8534
8526
  const names = [...CONTROL_LANE_TASK_NAMES];
8535
8527
  if (extra == null || extra === "") return names;
@@ -8592,6 +8584,7 @@ function ensureTasksRuntime(context, seed = {}) {
8592
8584
  if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
8593
8585
  if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
8594
8586
  if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
8587
+ if (rt.paused === void 0) rt.paused = seed.paused === true;
8595
8588
  return rt;
8596
8589
  }
8597
8590
  async function applyRuntimeParam(context, key, value) {
@@ -8656,6 +8649,140 @@ function readLoopRuntime(context) {
8656
8649
  };
8657
8650
  }
8658
8651
 
8652
+ // src/tasks/coreTasks/TaskPauseRunner.js
8653
+ async function applyRunnerPaused(context, paused) {
8654
+ const runtime = ensureTasksRuntime(context);
8655
+ const was = runtime.paused === true;
8656
+ runtime.paused = paused === true;
8657
+ const registry = context.servicesRegistry;
8658
+ if (registry && typeof registry === "object") {
8659
+ await updateServicesRegistryMetadata(context, registry, {
8660
+ paused: runtime.paused,
8661
+ pausedAt: runtime.paused ? (/* @__PURE__ */ new Date()).toISOString() : null
8662
+ });
8663
+ }
8664
+ 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).";
8665
+ context.logger?.warn?.(
8666
+ runtime.paused ? `[TaskPauseRunner] ${message}` : `[TaskUnpauseRunner] ${message}`
8667
+ );
8668
+ return {
8669
+ success: true,
8670
+ results: {
8671
+ paused: runtime.paused,
8672
+ message
8673
+ }
8674
+ };
8675
+ }
8676
+ var TaskPauseRunner = class extends AbstractTask {
8677
+ static taskName = "pauseRunner";
8678
+ static description = "Pause a runner: finish in-flight tasks, claim no new worker tasks until unpaused";
8679
+ static aliases = ["pause"];
8680
+ static defaultWaitForResult = true;
8681
+ /**
8682
+ * @param {object} context
8683
+ * @param {Record<string, unknown>} [overrides]
8684
+ * @returns {Promise<object>}
8685
+ */
8686
+ static async resolveParams(context, overrides = {}) {
8687
+ const main = await super.resolveParams(context, overrides);
8688
+ if (!main.serviceName) {
8689
+ throw new ParamError(
8690
+ "pause/pauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
8691
+ );
8692
+ }
8693
+ return main;
8694
+ }
8695
+ async run() {
8696
+ return applyRunnerPaused(this.context, true);
8697
+ }
8698
+ };
8699
+ var TaskUnpauseRunner = class extends AbstractTask {
8700
+ static taskName = "unpauseRunner";
8701
+ static description = "Unpause a runner: resume claiming worker tasks";
8702
+ static aliases = ["unpause"];
8703
+ static defaultWaitForResult = true;
8704
+ /**
8705
+ * @param {object} context
8706
+ * @param {Record<string, unknown>} [overrides]
8707
+ * @returns {Promise<object>}
8708
+ */
8709
+ static async resolveParams(context, overrides = {}) {
8710
+ const main = await super.resolveParams(context, overrides);
8711
+ if (!main.serviceName) {
8712
+ throw new ParamError(
8713
+ "unpause/unpauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
8714
+ );
8715
+ }
8716
+ return main;
8717
+ }
8718
+ async run() {
8719
+ return applyRunnerPaused(this.context, false);
8720
+ }
8721
+ };
8722
+
8723
+ // src/tasks/coreTasks/TaskGetLogs.js
8724
+ var TaskGetLogs = class extends AbstractTask {
8725
+ /**
8726
+ * @param {object} context
8727
+ * @param {Record<string, unknown>} [overrides]
8728
+ * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
8729
+ */
8730
+ static async resolveCustomParams(context, overrides = {}) {
8731
+ const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
8732
+ source: "string",
8733
+ resource: "string",
8734
+ tail: "number default 100",
8735
+ afterTs: "string"
8736
+ }, overrides);
8737
+ const source = typeof merged.source === "string" ? merged.source.trim() : "";
8738
+ const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
8739
+ if (!source) throw new ParamError('getLogs: param "source" is required');
8740
+ if (!resource) throw new ParamError('getLogs: param "resource" is required');
8741
+ let tail = Number(merged.tail);
8742
+ if (!Number.isFinite(tail) || tail < 1) tail = 100;
8743
+ tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
8744
+ const out = { source, resource, tail };
8745
+ if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
8746
+ out.afterTs = merged.afterTs.trim();
8747
+ }
8748
+ return out;
8749
+ }
8750
+ /**
8751
+ * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
8752
+ * @returns {Promise<{ success: boolean, results: unknown }>}
8753
+ */
8754
+ async run(_reportProgress) {
8755
+ const p = this.task.params ?? {};
8756
+ const source = String(p.source ?? "").trim();
8757
+ const resource = String(p.resource ?? "").trim();
8758
+ const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
8759
+ const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
8760
+ if (!source || !resource) {
8761
+ return {
8762
+ success: false,
8763
+ results: { error: 'getLogs requires params "source" and "resource"' }
8764
+ };
8765
+ }
8766
+ try {
8767
+ const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
8768
+ source,
8769
+ resource,
8770
+ tail,
8771
+ afterTs
8772
+ });
8773
+ return {
8774
+ success: true,
8775
+ results: { records, latestTs, source, resource }
8776
+ };
8777
+ } catch (e) {
8778
+ return {
8779
+ success: false,
8780
+ results: { error: e?.message ?? String(e) }
8781
+ };
8782
+ }
8783
+ }
8784
+ };
8785
+
8659
8786
  // src/tasks/coreTasks/TaskSetRuntimeParam.js
8660
8787
  var TaskSetRuntimeParam = class extends AbstractTask {
8661
8788
  static defaultWaitForResult = true;
@@ -8811,7 +8938,7 @@ var TasksRegistry = class _TasksRegistry {
8811
8938
  * @returns {TasksRegistry}
8812
8939
  */
8813
8940
  static withCoreTasks() {
8814
- 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);
8941
+ 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);
8815
8942
  }
8816
8943
  /**
8817
8944
  * Register a single task class under a name. Overwrites any previous entry.
@@ -8914,6 +9041,10 @@ var SERVICE_TASK_NAMES = [
8914
9041
  "ping",
8915
9042
  "stop",
8916
9043
  "stopRunner",
9044
+ "pause",
9045
+ "pauseRunner",
9046
+ "unpause",
9047
+ "unpauseRunner",
8917
9048
  "shellCommand",
8918
9049
  "systemInfo",
8919
9050
  "info",
@@ -9379,6 +9510,7 @@ async function runTasksLoop(context, options) {
9379
9510
  const defaultMeta = {
9380
9511
  component: "tasks-runner",
9381
9512
  allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
9513
+ paused: false,
9382
9514
  runtime: {
9383
9515
  maxParallel: loop0.maxParallel,
9384
9516
  pollMs: loop0.pollMs,
@@ -9445,28 +9577,38 @@ async function runTasksLoop(context, options) {
9445
9577
  if (claimJitterMs > 0) {
9446
9578
  await sleepMs(Math.floor(Math.random() * (claimJitterMs + 1)));
9447
9579
  }
9448
- while (runningPromises.size < maxParallel) {
9449
- const claimed = await claimNextRunnableTask(
9450
- context,
9451
- tasksTable,
9452
- target,
9453
- registry,
9454
- scanLimit,
9455
- allowedTasks,
9456
- runnerIdentity
9457
- );
9458
- if (!claimed) break;
9459
- const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
9460
- if (outcome.stopRunnerRequested && !stopRequested) {
9461
- stopRequested = true;
9462
- stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
9463
- context.tasksRunnerStop = true;
9464
- await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
9465
- }
9466
- }).finally(() => {
9467
- runningPromises.delete(p);
9468
- });
9469
- runningPromises.add(p);
9580
+ const paused = context.tasksRuntime?.paused === true;
9581
+ if (!paused) {
9582
+ while (runningPromises.size < maxParallel) {
9583
+ const claimed = await claimNextRunnableTask(
9584
+ context,
9585
+ tasksTable,
9586
+ target,
9587
+ registry,
9588
+ scanLimit,
9589
+ allowedTasks,
9590
+ runnerIdentity
9591
+ );
9592
+ if (!claimed) break;
9593
+ const p = executeClaimedTask(
9594
+ context,
9595
+ tasksTable,
9596
+ historyTable,
9597
+ claimed,
9598
+ registry,
9599
+ runningTaskInstances
9600
+ ).then(async (outcome) => {
9601
+ if (outcome.stopRunnerRequested && !stopRequested) {
9602
+ stopRequested = true;
9603
+ stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
9604
+ context.tasksRunnerStop = true;
9605
+ await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
9606
+ }
9607
+ }).finally(() => {
9608
+ runningPromises.delete(p);
9609
+ });
9610
+ runningPromises.add(p);
9611
+ }
9470
9612
  }
9471
9613
  const wakePromises = [...runningPromises];
9472
9614
  if (runningControlPromise) {
@@ -9520,16 +9662,26 @@ async function waitForTaskResult(context, taskId, options = {}) {
9520
9662
  const pollMs = options.pollMs ?? 500;
9521
9663
  const { tasksTable, historyTable } = queueToTableNames(queueName);
9522
9664
  const deadline = Date.now() + timeoutMs;
9523
- const waitStartedAt = /* @__PURE__ */ new Date();
9524
- let cachedNameOpid = null;
9525
- async function historySinceWait(name, opid) {
9526
- let q = db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
9665
+ const waitStartedAt = new Date(Date.now() - 5e3);
9666
+ let cachedNameOpid = options.name != null && String(options.name).trim() !== "" ? {
9667
+ name: String(options.name).trim(),
9668
+ opid: options.opid !== void 0 ? options.opid : null
9669
+ } : null;
9670
+ async function findHistory(name, opid) {
9671
+ const base = () => db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
9672
+ if (opid != null && String(opid).trim() !== "") {
9673
+ const byOpid = await base().where({ opid }).orderBy("completed_at", "desc").first();
9674
+ if (byOpid) return byOpid;
9675
+ }
9676
+ const byQueueId = await base().where({ opid: taskId }).orderBy("completed_at", "desc").first();
9677
+ if (byQueueId) return byQueueId;
9678
+ const byQueueIdAny = await db(historyTable).where({ name, opid: taskId }).orderBy("completed_at", "desc").first();
9679
+ if (byQueueIdAny) return byQueueIdAny;
9527
9680
  if (opid == null || opid === "") {
9528
- q = q.whereNull("opid");
9529
- } else {
9530
- q = q.where({ opid });
9681
+ const byNull = await base().whereNull("opid").orderBy("completed_at", "desc").first();
9682
+ if (byNull) return byNull;
9531
9683
  }
9532
- return await q.orderBy("completed_at", "desc").first();
9684
+ return void 0;
9533
9685
  }
9534
9686
  while (Date.now() <= deadline) {
9535
9687
  const legacy = await db(historyTable).where({ id: taskId }).orderBy("created_at", "desc").first();
@@ -9539,18 +9691,17 @@ async function waitForTaskResult(context, taskId, options = {}) {
9539
9691
  const pending = await db(tasksTable).where({ id: taskId }).first();
9540
9692
  if (pending) {
9541
9693
  cachedNameOpid = { name: pending.name, opid: pending.opid };
9542
- const done = await historySinceWait(pending.name, pending.opid);
9543
- if (done) {
9544
- return done;
9545
- }
9546
- } else if (cachedNameOpid) {
9547
- const done = await historySinceWait(cachedNameOpid.name, cachedNameOpid.opid);
9694
+ }
9695
+ if (cachedNameOpid) {
9696
+ const done = await findHistory(cachedNameOpid.name, cachedNameOpid.opid);
9548
9697
  if (done) {
9549
9698
  return done;
9550
9699
  }
9551
- return null;
9552
9700
  } else {
9553
- return null;
9701
+ const byQueueId = await db(historyTable).where({ opid: taskId }).orderBy("completed_at", "desc").first();
9702
+ if (byQueueId) {
9703
+ return byQueueId;
9704
+ }
9554
9705
  }
9555
9706
  await sleepMs(pollMs);
9556
9707
  }
@@ -9693,6 +9844,8 @@ var TasksManager = class _TasksManager {
9693
9844
  AbstractTask,
9694
9845
  Args,
9695
9846
  Aws,
9847
+ BAR_THUMB,
9848
+ BAR_TRACK,
9696
9849
  Box,
9697
9850
  Db,
9698
9851
  Divider,
@@ -9707,6 +9860,7 @@ var TasksManager = class _TasksManager {
9707
9860
  ListItem,
9708
9861
  MultiColumnListComponent,
9709
9862
  MultiColumnListWithPreviewComponent,
9863
+ PAGE_SCROLL_KEY_BINDINGS,
9710
9864
  Params,
9711
9865
  REMOTE_CLI_REL,
9712
9866
  React,
@@ -9720,6 +9874,7 @@ var TasksManager = class _TasksManager {
9720
9874
  ScreenTitle,
9721
9875
  ScrollableText,
9722
9876
  TaskGetLogs,
9877
+ TaskPauseRunner,
9723
9878
  TaskPing,
9724
9879
  TaskSampleProcess,
9725
9880
  TaskSetRuntimeParam,
@@ -9727,6 +9882,7 @@ var TasksManager = class _TasksManager {
9727
9882
  TaskStopRunner,
9728
9883
  TaskSumAB,
9729
9884
  TaskSystemInfo,
9885
+ TaskUnpauseRunner,
9730
9886
  TasksManager,
9731
9887
  TasksRegistry,
9732
9888
  Text,
@@ -9734,6 +9890,7 @@ var TasksManager = class _TasksManager {
9734
9890
  activateRelease,
9735
9891
  appendDeployLog,
9736
9892
  appendTaskIpcLog,
9893
+ applyRunnerPaused,
9737
9894
  applyRuntimeParam,
9738
9895
  applyRuntimePatch,
9739
9896
  bindingIdentity,