@nmakarov/cli-toolkit 0.69.0 → 0.71.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 ? BAR_THUMB : BAR_TRACK);
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);
@@ -1097,24 +1136,19 @@ function ScrollableText({
1097
1136
  ...rowNodes
1098
1137
  );
1099
1138
  }
1100
- var import_react5, import_ink5, h5, BAR_THUMB, BAR_TRACK, SCROLL_KEYS;
1139
+ var import_react5, import_ink5, h5, SCROLL_KEYS;
1101
1140
  var init_scrollable_text = __esm({
1102
1141
  "src/screen/scrollable-text.js"() {
1103
1142
  import_react5 = require("react");
1104
1143
  import_ink5 = require("ink");
1105
1144
  init_components();
1145
+ init_scrollbar();
1146
+ init_scrollbar();
1106
1147
  h5 = import_react5.createElement;
1107
- BAR_THUMB = "#";
1108
- BAR_TRACK = "|";
1109
1148
  SCROLL_KEYS = [
1110
1149
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1111
1150
  { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1112
- { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1113
- { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1114
- { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1115
- { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1116
- { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1117
- { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1151
+ ...PAGE_SCROLL_KEY_BINDINGS
1118
1152
  ];
1119
1153
  }
1120
1154
  });
@@ -1264,6 +1298,7 @@ var init_screen = __esm({
1264
1298
  init_components();
1265
1299
  init_ui_elements();
1266
1300
  init_scrollable_text();
1301
+ init_scrollbar();
1267
1302
  init_key_bindings();
1268
1303
  init_utils();
1269
1304
  init_footer_builder();
@@ -1281,7 +1316,10 @@ __export(src_exports, {
1281
1316
  AbstractTask: () => AbstractTask,
1282
1317
  Args: () => Args,
1283
1318
  Aws: () => Aws,
1319
+ BAR_THUMB: () => BAR_THUMB,
1320
+ BAR_TRACK: () => BAR_TRACK,
1284
1321
  Box: () => import_ink6.Box,
1322
+ DEFAULT_RUNTIME_PARAM_SPECS: () => DEFAULT_RUNTIME_PARAM_SPECS,
1285
1323
  Db: () => Db,
1286
1324
  Divider: () => Divider,
1287
1325
  FileDatabase: () => FileDatabase,
@@ -1295,6 +1333,7 @@ __export(src_exports, {
1295
1333
  ListItem: () => ListItem,
1296
1334
  MultiColumnListComponent: () => MultiColumnListComponent,
1297
1335
  MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
1336
+ PAGE_SCROLL_KEY_BINDINGS: () => PAGE_SCROLL_KEY_BINDINGS,
1298
1337
  Params: () => Params,
1299
1338
  REMOTE_CLI_REL: () => REMOTE_CLI_REL,
1300
1339
  React: () => import_react6.default,
@@ -1308,6 +1347,7 @@ __export(src_exports, {
1308
1347
  ScreenTitle: () => ScreenTitle,
1309
1348
  ScrollableText: () => ScrollableText,
1310
1349
  TaskGetLogs: () => TaskGetLogs,
1350
+ TaskPauseRunner: () => TaskPauseRunner,
1311
1351
  TaskPing: () => TaskPing,
1312
1352
  TaskSampleProcess: () => TaskSampleProcess,
1313
1353
  TaskSetRuntimeParam: () => TaskSetRuntimeParam,
@@ -1315,6 +1355,7 @@ __export(src_exports, {
1315
1355
  TaskStopRunner: () => TaskStopRunner,
1316
1356
  TaskSumAB: () => TaskSumAB,
1317
1357
  TaskSystemInfo: () => TaskSystemInfo,
1358
+ TaskUnpauseRunner: () => TaskUnpauseRunner,
1318
1359
  TasksManager: () => TasksManager,
1319
1360
  TasksRegistry: () => TasksRegistry,
1320
1361
  Text: () => import_ink6.Text,
@@ -1322,6 +1363,7 @@ __export(src_exports, {
1322
1363
  activateRelease: () => activateRelease,
1323
1364
  appendDeployLog: () => appendDeployLog,
1324
1365
  appendTaskIpcLog: () => appendTaskIpcLog,
1366
+ applyRunnerPaused: () => applyRunnerPaused,
1325
1367
  applyRuntimeParam: () => applyRuntimeParam,
1326
1368
  applyRuntimePatch: () => applyRuntimePatch,
1327
1369
  bindingIdentity: () => bindingIdentity,
@@ -1380,6 +1422,7 @@ __export(src_exports, {
1380
1422
  matchesParsedPattern: () => matchesParsedPattern,
1381
1423
  memo: () => import_react6.memo,
1382
1424
  mergeAllowedTasksWithServiceTasks: () => mergeAllowedTasksWithServiceTasks,
1425
+ mergeRuntimeParamSpecs: () => mergeRuntimeParamSpecs,
1383
1426
  nextTimeMatch: () => nextTimeMatch,
1384
1427
  normalizeAllowedTasks: () => normalizeAllowedTasks,
1385
1428
  npmEnv: () => npmEnv,
@@ -1416,6 +1459,8 @@ __export(src_exports, {
1416
1459
  runRemoteStatus: () => runRemoteStatus,
1417
1460
  runShell: () => runShell,
1418
1461
  runTasksLoop: () => runTasksLoop,
1462
+ runtimeParamSpecsFromMetadata: () => runtimeParamSpecsFromMetadata,
1463
+ runtimeValuesForSpecs: () => runtimeValuesForSpecs,
1419
1464
  scrollbarGlyphs: () => scrollbarGlyphs,
1420
1465
  scrubEnvContent: () => scrubEnvContent,
1421
1466
  servicePaths: () => servicePaths,
@@ -7189,12 +7234,13 @@ function tasksSchemaSpec(queueName = "tasks") {
7189
7234
  }
7190
7235
  };
7191
7236
  }
7192
- function taskHistoryInsertFromQueueRow(row, overrides) {
7237
+ function taskHistoryInsertFromQueueRow(row, overrides = {}) {
7193
7238
  const { id, ...snapshot } = row;
7194
- void id;
7239
+ const opid = overrides.opid !== void 0 ? overrides.opid : snapshot.opid != null && String(snapshot.opid).trim() !== "" ? snapshot.opid : id;
7195
7240
  return {
7196
7241
  ...snapshot,
7197
- ...overrides
7242
+ ...overrides,
7243
+ opid
7198
7244
  };
7199
7245
  }
7200
7246
  async function ensureTaskTables(context, options = {}) {
@@ -8457,69 +8503,6 @@ var TaskStopRunner = class extends AbstractTask {
8457
8503
  }
8458
8504
  };
8459
8505
 
8460
- // src/tasks/coreTasks/TaskGetLogs.js
8461
- var TaskGetLogs = class extends AbstractTask {
8462
- /**
8463
- * @param {object} context
8464
- * @param {Record<string, unknown>} [overrides]
8465
- * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
8466
- */
8467
- static async resolveCustomParams(context, overrides = {}) {
8468
- const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
8469
- source: "string",
8470
- resource: "string",
8471
- tail: "number default 100",
8472
- afterTs: "string"
8473
- }, overrides);
8474
- const source = typeof merged.source === "string" ? merged.source.trim() : "";
8475
- const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
8476
- if (!source) throw new ParamError('getLogs: param "source" is required');
8477
- if (!resource) throw new ParamError('getLogs: param "resource" is required');
8478
- let tail = Number(merged.tail);
8479
- if (!Number.isFinite(tail) || tail < 1) tail = 100;
8480
- tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
8481
- const out = { source, resource, tail };
8482
- if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
8483
- out.afterTs = merged.afterTs.trim();
8484
- }
8485
- return out;
8486
- }
8487
- /**
8488
- * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
8489
- * @returns {Promise<{ success: boolean, results: unknown }>}
8490
- */
8491
- async run(_reportProgress) {
8492
- const p = this.task.params ?? {};
8493
- const source = String(p.source ?? "").trim();
8494
- const resource = String(p.resource ?? "").trim();
8495
- const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
8496
- const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
8497
- if (!source || !resource) {
8498
- return {
8499
- success: false,
8500
- results: { error: 'getLogs requires params "source" and "resource"' }
8501
- };
8502
- }
8503
- try {
8504
- const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
8505
- source,
8506
- resource,
8507
- tail,
8508
- afterTs
8509
- });
8510
- return {
8511
- success: true,
8512
- results: { records, latestTs, source, resource }
8513
- };
8514
- } catch (e) {
8515
- return {
8516
- success: false,
8517
- results: { error: e?.message ?? String(e) }
8518
- };
8519
- }
8520
- }
8521
- };
8522
-
8523
8506
  // src/tasks/runtimeParams.js
8524
8507
  var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
8525
8508
  var LOGGER_RUNTIME_KEYS = [
@@ -8533,7 +8516,77 @@ var LOGGER_RUNTIME_KEYS = [
8533
8516
  "progressWithTimes",
8534
8517
  "progressThrottleMs"
8535
8518
  ];
8536
- var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
8519
+ var DEFAULT_RUNTIME_PARAM_SPECS = [
8520
+ {
8521
+ key: "maxParallel",
8522
+ type: "number",
8523
+ label: "Max parallel",
8524
+ description: "Worker-lane concurrency (how many tasks claim at once)"
8525
+ },
8526
+ {
8527
+ key: "pollMs",
8528
+ type: "number",
8529
+ label: "Poll ms",
8530
+ description: "Idle poll interval between claim attempts"
8531
+ },
8532
+ {
8533
+ key: "claimJitterMs",
8534
+ type: "number",
8535
+ label: "Claim jitter ms",
8536
+ description: "Random delay before worker claims (0 = off)"
8537
+ },
8538
+ {
8539
+ key: "scanLimit",
8540
+ type: "number",
8541
+ label: "Scan limit",
8542
+ description: "Max idle rows scanned per claim attempt"
8543
+ }
8544
+ ];
8545
+ function mergeRuntimeParamSpecs(extra) {
8546
+ const byKey = new Map(DEFAULT_RUNTIME_PARAM_SPECS.map((s) => [s.key, { ...s }]));
8547
+ if (Array.isArray(extra)) {
8548
+ for (const raw of extra) {
8549
+ if (!raw || typeof raw !== "object") continue;
8550
+ const key = String(raw.key ?? "").trim();
8551
+ if (!key) continue;
8552
+ const prev = byKey.get(key) ?? {};
8553
+ const type = ["number", "boolean", "string"].includes(raw.type) ? raw.type : prev.type ?? "string";
8554
+ byKey.set(key, {
8555
+ key,
8556
+ type,
8557
+ label: String(raw.label ?? prev.label ?? key),
8558
+ description: raw.description != null ? String(raw.description) : prev.description != null ? String(prev.description) : void 0
8559
+ });
8560
+ }
8561
+ }
8562
+ return Array.from(byKey.values());
8563
+ }
8564
+ function runtimeValuesForSpecs(context, specs = DEFAULT_RUNTIME_PARAM_SPECS) {
8565
+ const rt = ensureTasksRuntime(context);
8566
+ const out = {};
8567
+ for (const s of specs) {
8568
+ if (rt[s.key] !== void 0) out[s.key] = rt[s.key];
8569
+ }
8570
+ return out;
8571
+ }
8572
+ function runtimeParamSpecsFromMetadata(metadata) {
8573
+ const meta = metadata && typeof metadata === "object" ? metadata : null;
8574
+ const raw = meta?.runtimeParams;
8575
+ if (!Array.isArray(raw) || raw.length === 0) {
8576
+ return mergeRuntimeParamSpecs();
8577
+ }
8578
+ return mergeRuntimeParamSpecs(raw);
8579
+ }
8580
+ var CONTROL_LANE_TASK_NAMES = [
8581
+ "stopRunner",
8582
+ "stop",
8583
+ "pauseRunner",
8584
+ "pause",
8585
+ "unpauseRunner",
8586
+ "unpause",
8587
+ "setRuntimeParam",
8588
+ "setRunnerParam"
8589
+ ];
8537
8590
  function controlLaneTaskNames(extra) {
8538
8591
  const names = [...CONTROL_LANE_TASK_NAMES];
8539
8592
  if (extra == null || extra === "") return names;
@@ -8596,6 +8649,7 @@ function ensureTasksRuntime(context, seed = {}) {
8596
8649
  if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
8597
8650
  if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
8598
8651
  if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
8652
+ if (rt.paused === void 0) rt.paused = seed.paused === true;
8599
8653
  return rt;
8600
8654
  }
8601
8655
  async function applyRuntimeParam(context, key, value) {
@@ -8619,12 +8673,15 @@ async function applyRuntimeParam(context, key, value) {
8619
8673
  const reg = context.servicesRegistry;
8620
8674
  if (reg?.rowId && reg?.registryTable) {
8621
8675
  try {
8622
- const loopSnapshot = {};
8676
+ const specs = Array.isArray(context.tasksRuntimeParamSpecs) ? context.tasksRuntimeParamSpecs : DEFAULT_RUNTIME_PARAM_SPECS;
8677
+ const runtimeSnapshot = runtimeValuesForSpecs(context, specs);
8623
8678
  for (const lk of LOOP_RUNTIME_KEYS) {
8624
- if (runtime[lk] !== void 0) loopSnapshot[lk] = runtime[lk];
8679
+ if (runtime[lk] !== void 0 && runtimeSnapshot[lk] === void 0) {
8680
+ runtimeSnapshot[lk] = runtime[lk];
8681
+ }
8625
8682
  }
8626
8683
  await updateServicesRegistryMetadata(context, reg, {
8627
- runtime: loopSnapshot,
8684
+ runtime: runtimeSnapshot,
8628
8685
  runtimeUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
8629
8686
  });
8630
8687
  applied.push("servicesRegistry");
@@ -8660,6 +8717,140 @@ function readLoopRuntime(context) {
8660
8717
  };
8661
8718
  }
8662
8719
 
8720
+ // src/tasks/coreTasks/TaskPauseRunner.js
8721
+ async function applyRunnerPaused(context, paused) {
8722
+ const runtime = ensureTasksRuntime(context);
8723
+ const was = runtime.paused === true;
8724
+ runtime.paused = paused === true;
8725
+ const registry = context.servicesRegistry;
8726
+ if (registry && typeof registry === "object") {
8727
+ await updateServicesRegistryMetadata(context, registry, {
8728
+ paused: runtime.paused,
8729
+ pausedAt: runtime.paused ? (/* @__PURE__ */ new Date()).toISOString() : null
8730
+ });
8731
+ }
8732
+ 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).";
8733
+ context.logger?.warn?.(
8734
+ runtime.paused ? `[TaskPauseRunner] ${message}` : `[TaskUnpauseRunner] ${message}`
8735
+ );
8736
+ return {
8737
+ success: true,
8738
+ results: {
8739
+ paused: runtime.paused,
8740
+ message
8741
+ }
8742
+ };
8743
+ }
8744
+ var TaskPauseRunner = class extends AbstractTask {
8745
+ static taskName = "pauseRunner";
8746
+ static description = "Pause a runner: finish in-flight tasks, claim no new worker tasks until unpaused";
8747
+ static aliases = ["pause"];
8748
+ static defaultWaitForResult = true;
8749
+ /**
8750
+ * @param {object} context
8751
+ * @param {Record<string, unknown>} [overrides]
8752
+ * @returns {Promise<object>}
8753
+ */
8754
+ static async resolveParams(context, overrides = {}) {
8755
+ const main = await super.resolveParams(context, overrides);
8756
+ if (!main.serviceName) {
8757
+ throw new ParamError(
8758
+ "pause/pauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
8759
+ );
8760
+ }
8761
+ return main;
8762
+ }
8763
+ async run() {
8764
+ return applyRunnerPaused(this.context, true);
8765
+ }
8766
+ };
8767
+ var TaskUnpauseRunner = class extends AbstractTask {
8768
+ static taskName = "unpauseRunner";
8769
+ static description = "Unpause a runner: resume claiming worker tasks";
8770
+ static aliases = ["unpause"];
8771
+ static defaultWaitForResult = true;
8772
+ /**
8773
+ * @param {object} context
8774
+ * @param {Record<string, unknown>} [overrides]
8775
+ * @returns {Promise<object>}
8776
+ */
8777
+ static async resolveParams(context, overrides = {}) {
8778
+ const main = await super.resolveParams(context, overrides);
8779
+ if (!main.serviceName) {
8780
+ throw new ParamError(
8781
+ "unpause/unpauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
8782
+ );
8783
+ }
8784
+ return main;
8785
+ }
8786
+ async run() {
8787
+ return applyRunnerPaused(this.context, false);
8788
+ }
8789
+ };
8790
+
8791
+ // src/tasks/coreTasks/TaskGetLogs.js
8792
+ var TaskGetLogs = class extends AbstractTask {
8793
+ /**
8794
+ * @param {object} context
8795
+ * @param {Record<string, unknown>} [overrides]
8796
+ * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
8797
+ */
8798
+ static async resolveCustomParams(context, overrides = {}) {
8799
+ const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
8800
+ source: "string",
8801
+ resource: "string",
8802
+ tail: "number default 100",
8803
+ afterTs: "string"
8804
+ }, overrides);
8805
+ const source = typeof merged.source === "string" ? merged.source.trim() : "";
8806
+ const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
8807
+ if (!source) throw new ParamError('getLogs: param "source" is required');
8808
+ if (!resource) throw new ParamError('getLogs: param "resource" is required');
8809
+ let tail = Number(merged.tail);
8810
+ if (!Number.isFinite(tail) || tail < 1) tail = 100;
8811
+ tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
8812
+ const out = { source, resource, tail };
8813
+ if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
8814
+ out.afterTs = merged.afterTs.trim();
8815
+ }
8816
+ return out;
8817
+ }
8818
+ /**
8819
+ * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
8820
+ * @returns {Promise<{ success: boolean, results: unknown }>}
8821
+ */
8822
+ async run(_reportProgress) {
8823
+ const p = this.task.params ?? {};
8824
+ const source = String(p.source ?? "").trim();
8825
+ const resource = String(p.resource ?? "").trim();
8826
+ const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
8827
+ const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
8828
+ if (!source || !resource) {
8829
+ return {
8830
+ success: false,
8831
+ results: { error: 'getLogs requires params "source" and "resource"' }
8832
+ };
8833
+ }
8834
+ try {
8835
+ const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
8836
+ source,
8837
+ resource,
8838
+ tail,
8839
+ afterTs
8840
+ });
8841
+ return {
8842
+ success: true,
8843
+ results: { records, latestTs, source, resource }
8844
+ };
8845
+ } catch (e) {
8846
+ return {
8847
+ success: false,
8848
+ results: { error: e?.message ?? String(e) }
8849
+ };
8850
+ }
8851
+ }
8852
+ };
8853
+
8663
8854
  // src/tasks/coreTasks/TaskSetRuntimeParam.js
8664
8855
  var TaskSetRuntimeParam = class extends AbstractTask {
8665
8856
  static defaultWaitForResult = true;
@@ -8815,7 +9006,7 @@ var TasksRegistry = class _TasksRegistry {
8815
9006
  * @returns {TasksRegistry}
8816
9007
  */
8817
9008
  static withCoreTasks() {
8818
- 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);
9009
+ 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);
8819
9010
  }
8820
9011
  /**
8821
9012
  * Register a single task class under a name. Overwrites any previous entry.
@@ -8918,6 +9109,10 @@ var SERVICE_TASK_NAMES = [
8918
9109
  "ping",
8919
9110
  "stop",
8920
9111
  "stopRunner",
9112
+ "pause",
9113
+ "pauseRunner",
9114
+ "unpause",
9115
+ "unpauseRunner",
8921
9116
  "shellCommand",
8922
9117
  "systemInfo",
8923
9118
  "info",
@@ -9379,17 +9574,25 @@ async function runTasksLoop(context, options) {
9379
9574
  if (hbGroup) {
9380
9575
  const hbIntervalMs = options.runnerHeartbeatIntervalMs ?? 1e4;
9381
9576
  const staleMs = options.runnerHeartbeatStaleMs ?? 45e3;
9382
- const loop0 = readLoopRuntime(context);
9577
+ const runtimeParamSpecs = mergeRuntimeParamSpecs(options.runnerRuntimeParams);
9578
+ context.tasksRuntimeParamSpecs = runtimeParamSpecs;
9383
9579
  const defaultMeta = {
9384
9580
  component: "tasks-runner",
9385
9581
  allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
9386
- runtime: {
9387
- maxParallel: loop0.maxParallel,
9388
- pollMs: loop0.pollMs,
9389
- claimJitterMs: loop0.claimJitterMs,
9390
- scanLimit: loop0.scanLimit
9391
- }
9582
+ paused: false,
9583
+ runtimeParams: runtimeParamSpecs,
9584
+ runtime: runtimeValuesForSpecs(context, runtimeParamSpecs)
9392
9585
  };
9586
+ const metadata = {
9587
+ ...defaultMeta,
9588
+ ...options.runnerMetadata && typeof options.runnerMetadata === "object" ? options.runnerMetadata : {}
9589
+ };
9590
+ if (!Array.isArray(metadata.runtimeParams) || metadata.runtimeParams.length === 0) {
9591
+ metadata.runtimeParams = defaultMeta.runtimeParams;
9592
+ }
9593
+ if (!metadata.runtime || typeof metadata.runtime !== "object") {
9594
+ metadata.runtime = defaultMeta.runtime;
9595
+ }
9393
9596
  registryReg = await registerInServicesRegistry(context, {
9394
9597
  queueName,
9395
9598
  target,
@@ -9399,7 +9602,7 @@ async function runTasksLoop(context, options) {
9399
9602
  staleMs,
9400
9603
  groupMaxInstances: options.runnerGroupMaxInstances,
9401
9604
  enforceMaxInstances: options.runnerEnforceMaxInstances ?? true,
9402
- metadata: options.runnerMetadata ?? defaultMeta
9605
+ metadata
9403
9606
  });
9404
9607
  context.servicesRegistry = registryReg;
9405
9608
  runnerIdentity = {
@@ -9449,28 +9652,38 @@ async function runTasksLoop(context, options) {
9449
9652
  if (claimJitterMs > 0) {
9450
9653
  await sleepMs(Math.floor(Math.random() * (claimJitterMs + 1)));
9451
9654
  }
9452
- while (runningPromises.size < maxParallel) {
9453
- const claimed = await claimNextRunnableTask(
9454
- context,
9455
- tasksTable,
9456
- target,
9457
- registry,
9458
- scanLimit,
9459
- allowedTasks,
9460
- runnerIdentity
9461
- );
9462
- if (!claimed) break;
9463
- const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
9464
- if (outcome.stopRunnerRequested && !stopRequested) {
9465
- stopRequested = true;
9466
- stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
9467
- context.tasksRunnerStop = true;
9468
- await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
9469
- }
9470
- }).finally(() => {
9471
- runningPromises.delete(p);
9472
- });
9473
- runningPromises.add(p);
9655
+ const paused = context.tasksRuntime?.paused === true;
9656
+ if (!paused) {
9657
+ while (runningPromises.size < maxParallel) {
9658
+ const claimed = await claimNextRunnableTask(
9659
+ context,
9660
+ tasksTable,
9661
+ target,
9662
+ registry,
9663
+ scanLimit,
9664
+ allowedTasks,
9665
+ runnerIdentity
9666
+ );
9667
+ if (!claimed) break;
9668
+ const p = executeClaimedTask(
9669
+ context,
9670
+ tasksTable,
9671
+ historyTable,
9672
+ claimed,
9673
+ registry,
9674
+ runningTaskInstances
9675
+ ).then(async (outcome) => {
9676
+ if (outcome.stopRunnerRequested && !stopRequested) {
9677
+ stopRequested = true;
9678
+ stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
9679
+ context.tasksRunnerStop = true;
9680
+ await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
9681
+ }
9682
+ }).finally(() => {
9683
+ runningPromises.delete(p);
9684
+ });
9685
+ runningPromises.add(p);
9686
+ }
9474
9687
  }
9475
9688
  const wakePromises = [...runningPromises];
9476
9689
  if (runningControlPromise) {
@@ -9524,16 +9737,26 @@ async function waitForTaskResult(context, taskId, options = {}) {
9524
9737
  const pollMs = options.pollMs ?? 500;
9525
9738
  const { tasksTable, historyTable } = queueToTableNames(queueName);
9526
9739
  const deadline = Date.now() + timeoutMs;
9527
- const waitStartedAt = /* @__PURE__ */ new Date();
9528
- let cachedNameOpid = null;
9529
- async function historySinceWait(name, opid) {
9530
- let q = db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
9740
+ const waitStartedAt = new Date(Date.now() - 5e3);
9741
+ let cachedNameOpid = options.name != null && String(options.name).trim() !== "" ? {
9742
+ name: String(options.name).trim(),
9743
+ opid: options.opid !== void 0 ? options.opid : null
9744
+ } : null;
9745
+ async function findHistory(name, opid) {
9746
+ const base = () => db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
9747
+ if (opid != null && String(opid).trim() !== "") {
9748
+ const byOpid = await base().where({ opid }).orderBy("completed_at", "desc").first();
9749
+ if (byOpid) return byOpid;
9750
+ }
9751
+ const byQueueId = await base().where({ opid: taskId }).orderBy("completed_at", "desc").first();
9752
+ if (byQueueId) return byQueueId;
9753
+ const byQueueIdAny = await db(historyTable).where({ name, opid: taskId }).orderBy("completed_at", "desc").first();
9754
+ if (byQueueIdAny) return byQueueIdAny;
9531
9755
  if (opid == null || opid === "") {
9532
- q = q.whereNull("opid");
9533
- } else {
9534
- q = q.where({ opid });
9756
+ const byNull = await base().whereNull("opid").orderBy("completed_at", "desc").first();
9757
+ if (byNull) return byNull;
9535
9758
  }
9536
- return await q.orderBy("completed_at", "desc").first();
9759
+ return void 0;
9537
9760
  }
9538
9761
  while (Date.now() <= deadline) {
9539
9762
  const legacy = await db(historyTable).where({ id: taskId }).orderBy("created_at", "desc").first();
@@ -9543,18 +9766,17 @@ async function waitForTaskResult(context, taskId, options = {}) {
9543
9766
  const pending = await db(tasksTable).where({ id: taskId }).first();
9544
9767
  if (pending) {
9545
9768
  cachedNameOpid = { name: pending.name, opid: pending.opid };
9546
- const done = await historySinceWait(pending.name, pending.opid);
9547
- if (done) {
9548
- return done;
9549
- }
9550
- } else if (cachedNameOpid) {
9551
- const done = await historySinceWait(cachedNameOpid.name, cachedNameOpid.opid);
9769
+ }
9770
+ if (cachedNameOpid) {
9771
+ const done = await findHistory(cachedNameOpid.name, cachedNameOpid.opid);
9552
9772
  if (done) {
9553
9773
  return done;
9554
9774
  }
9555
- return null;
9556
9775
  } else {
9557
- return null;
9776
+ const byQueueId = await db(historyTable).where({ opid: taskId }).orderBy("completed_at", "desc").first();
9777
+ if (byQueueId) {
9778
+ return byQueueId;
9779
+ }
9558
9780
  }
9559
9781
  await sleepMs(pollMs);
9560
9782
  }
@@ -9697,7 +9919,10 @@ var TasksManager = class _TasksManager {
9697
9919
  AbstractTask,
9698
9920
  Args,
9699
9921
  Aws,
9922
+ BAR_THUMB,
9923
+ BAR_TRACK,
9700
9924
  Box,
9925
+ DEFAULT_RUNTIME_PARAM_SPECS,
9701
9926
  Db,
9702
9927
  Divider,
9703
9928
  FileDatabase,
@@ -9711,6 +9936,7 @@ var TasksManager = class _TasksManager {
9711
9936
  ListItem,
9712
9937
  MultiColumnListComponent,
9713
9938
  MultiColumnListWithPreviewComponent,
9939
+ PAGE_SCROLL_KEY_BINDINGS,
9714
9940
  Params,
9715
9941
  REMOTE_CLI_REL,
9716
9942
  React,
@@ -9724,6 +9950,7 @@ var TasksManager = class _TasksManager {
9724
9950
  ScreenTitle,
9725
9951
  ScrollableText,
9726
9952
  TaskGetLogs,
9953
+ TaskPauseRunner,
9727
9954
  TaskPing,
9728
9955
  TaskSampleProcess,
9729
9956
  TaskSetRuntimeParam,
@@ -9731,6 +9958,7 @@ var TasksManager = class _TasksManager {
9731
9958
  TaskStopRunner,
9732
9959
  TaskSumAB,
9733
9960
  TaskSystemInfo,
9961
+ TaskUnpauseRunner,
9734
9962
  TasksManager,
9735
9963
  TasksRegistry,
9736
9964
  Text,
@@ -9738,6 +9966,7 @@ var TasksManager = class _TasksManager {
9738
9966
  activateRelease,
9739
9967
  appendDeployLog,
9740
9968
  appendTaskIpcLog,
9969
+ applyRunnerPaused,
9741
9970
  applyRuntimeParam,
9742
9971
  applyRuntimePatch,
9743
9972
  bindingIdentity,
@@ -9796,6 +10025,7 @@ var TasksManager = class _TasksManager {
9796
10025
  matchesParsedPattern,
9797
10026
  memo,
9798
10027
  mergeAllowedTasksWithServiceTasks,
10028
+ mergeRuntimeParamSpecs,
9799
10029
  nextTimeMatch,
9800
10030
  normalizeAllowedTasks,
9801
10031
  npmEnv,
@@ -9832,6 +10062,8 @@ var TasksManager = class _TasksManager {
9832
10062
  runRemoteStatus,
9833
10063
  runShell,
9834
10064
  runTasksLoop,
10065
+ runtimeParamSpecsFromMetadata,
10066
+ runtimeValuesForSpecs,
9835
10067
  scrollbarGlyphs,
9836
10068
  scrubEnvContent,
9837
10069
  servicePaths,