@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.js CHANGED
@@ -88,9 +88,48 @@ var init_components = __esm({
88
88
  }
89
89
  });
90
90
 
91
+ // src/screen/scrollbar.js
92
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
93
+ const view = Math.max(1, Math.floor(viewportRows));
94
+ const total = Math.max(0, Math.floor(totalLines));
95
+ if (total <= view) return null;
96
+ const maxScroll = total - view;
97
+ const thumbSize = Math.max(1, Math.round(view / total * view));
98
+ const travel = Math.max(0, view - thumbSize);
99
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
100
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
101
+ const glyphs = [];
102
+ for (let i = 0; i < view; i++) {
103
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
104
+ }
105
+ return glyphs;
106
+ }
107
+ var BAR_THUMB, BAR_TRACK, PAGE_SCROLL_KEY_BINDINGS;
108
+ var init_scrollbar = __esm({
109
+ "src/screen/scrollbar.js"() {
110
+ BAR_THUMB = "#";
111
+ BAR_TRACK = "|";
112
+ PAGE_SCROLL_KEY_BINDINGS = [
113
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
114
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
115
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
116
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
117
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
118
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
119
+ ];
120
+ }
121
+ });
122
+
91
123
  // src/screen/list-components.js
92
124
  import React, { useState, useEffect, useRef, createElement } from "react";
93
125
  import { Box as Box2, Text as Text2 } from "ink";
126
+ function clipNameForScrollbar(name, needsBar) {
127
+ if (!needsBar || name == null) return name;
128
+ const s = String(name);
129
+ if (s.length <= 1) return s;
130
+ if (/\s$/.test(s)) return s.slice(0, -1);
131
+ return s;
132
+ }
94
133
  function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
95
134
  const [, forceUpdate] = useState({});
96
135
  const termWidth = (process.stdout.columns || 80) - 8;
@@ -304,17 +343,42 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
304
343
  });
305
344
  ctx.setAction("scrollUp", () => {
306
345
  const { scrollOffset: currentScrollOffset } = scrollStateRef.current;
307
- const newScrollOffset = Math.max(0, currentScrollOffset - 1);
308
- setScrollOffset(newScrollOffset);
346
+ setScrollOffset(Math.max(0, currentScrollOffset - 1));
309
347
  forceUpdate({});
310
348
  });
311
349
  ctx.setAction("scrollDown", () => {
312
350
  const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
313
351
  const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
314
- const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
315
- setScrollOffset(newScrollOffset);
352
+ setScrollOffset(Math.min(currentMaxScrollOffset, currentScrollOffset + 1));
353
+ forceUpdate({});
354
+ });
355
+ ctx.setAction("pageUp", () => {
356
+ const { maxHeight: vh } = scrollStateRef.current;
357
+ const page = Math.max(1, vh || 1);
358
+ const newIndex = Math.max(0, selectedIndexRef.current - page);
359
+ selectedIndexRef.current = newIndex;
360
+ const list = displayItemsRef.current;
361
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
362
+ setScrollOffset(newIndex);
363
+ forceUpdate({});
364
+ });
365
+ ctx.setAction("pageDown", () => {
366
+ const { maxHeight: vh, totalItems } = scrollStateRef.current;
367
+ const page = Math.max(1, vh || 1);
368
+ const maxIndex = Math.max(0, totalItems - 1);
369
+ const newIndex = Math.min(maxIndex, selectedIndexRef.current + page);
370
+ selectedIndexRef.current = newIndex;
371
+ const list = displayItemsRef.current;
372
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
373
+ const maxScroll = Math.max(0, totalItems - page);
374
+ setScrollOffset(Math.min(maxScroll, Math.max(0, newIndex - page + 1)));
316
375
  forceUpdate({});
317
376
  });
377
+ const navBindings = [
378
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
379
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
380
+ ...PAGE_SCROLL_KEY_BINDINGS
381
+ ];
318
382
  if (sortable) {
319
383
  ctx.setAction("toggleSort", () => {
320
384
  const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
@@ -361,8 +425,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
361
425
  }
362
426
  };
363
427
  ctx.setKeyBinding([
364
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
365
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
428
+ ...navBindings,
366
429
  {
367
430
  key: "s",
368
431
  caption: sortCaption,
@@ -372,47 +435,47 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
372
435
  ]);
373
436
  ctx.update();
374
437
  } else {
375
- ctx.setKeyBinding([
376
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
377
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
378
- ]);
438
+ ctx.setKeyBinding(navBindings);
379
439
  }
380
440
  }, [sortOrder, sortable]);
381
441
  const selectedIndex = selectedIndexRef.current;
442
+ const needsBar = displayItems.length > effectiveMaxHeight;
443
+ const barGlyphs = needsBar ? scrollbarGlyphs(effectiveMaxHeight, displayItems.length, clampedScrollOffset) : null;
444
+ const appendBar = (rowContent, displayIndex) => {
445
+ if (!barGlyphs) return rowContent;
446
+ const glyph = barGlyphs[displayIndex] ?? BAR_TRACK;
447
+ return h2(
448
+ Box2,
449
+ { flexDirection: "row" },
450
+ rowContent,
451
+ h2(Text2, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
452
+ );
453
+ };
382
454
  const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
383
455
  const isFirstVisible = displayIndex === 0;
384
456
  const isLastVisible = displayIndex === visibleItems.length - 1;
385
- let arrowPrefix = "";
386
- let selectionPrefix = "";
387
- if (isFirstVisible && canScrollUp) {
388
- arrowPrefix = "\u2191 ";
389
- } else if (isLastVisible && canScrollDown) {
390
- arrowPrefix = "\u2193 ";
391
- } else {
392
- arrowPrefix = " ";
393
- }
394
- if (isSelected) {
395
- selectionPrefix = selectionMarker;
396
- } else {
397
- selectionPrefix = " ".repeat(selectionMarker.length);
457
+ let arrowPrefix = " ";
458
+ if (!needsBar) {
459
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
460
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
398
461
  }
462
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
463
+ const label = clipNameForScrollbar(item.name, needsBar);
399
464
  return h2(
400
465
  Box2,
401
466
  { flexDirection: "row" },
402
- // Arrow (clickable if functional, not highlighted)
403
- h2(Text2, {
404
- key: `arrow-${actualIndex}`,
405
- color: "white"
406
- }, arrowPrefix),
407
- // Selection marker space (always same width, not highlighted)
467
+ h2(Text2, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
408
468
  h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
409
- // Item name (highlighted if selected)
410
- h2(Text2, {
411
- key: `name-${actualIndex}`,
412
- color: isSelected ? "black" : "white",
413
- backgroundColor: isSelected ? "cyan" : void 0,
414
- bold: isSelected
415
- }, item.name)
469
+ h2(
470
+ Text2,
471
+ {
472
+ key: `name-${actualIndex}`,
473
+ color: isSelected ? "black" : "white",
474
+ backgroundColor: isSelected ? "cyan" : void 0,
475
+ bold: isSelected
476
+ },
477
+ label
478
+ )
416
479
  );
417
480
  };
418
481
  const itemRenderer = renderItem || defaultRenderItem;
@@ -425,42 +488,32 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
425
488
  if (renderItem) {
426
489
  const isFirstVisible = displayIndex === 0;
427
490
  const isLastVisible = displayIndex === visibleItems.length - 1;
428
- let arrowPrefix = "";
429
- let selectionPrefix = "";
430
- if (isFirstVisible && canScrollUp) {
431
- arrowPrefix = "\u2191 ";
432
- } else if (isLastVisible && canScrollDown) {
433
- arrowPrefix = "\u2193 ";
434
- } else {
435
- arrowPrefix = " ";
491
+ let arrowPrefix = " ";
492
+ if (!needsBar) {
493
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
494
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
436
495
  }
437
- if (isSelected) {
438
- selectionPrefix = selectionMarker;
439
- } else {
440
- selectionPrefix = " ".repeat(selectionMarker.length);
441
- }
442
- return h2(ScreenRow, {
443
- key: `item-${actualIndex}`,
444
- children: h2(
445
- Box2,
446
- { flexDirection: "row" },
447
- // Arrow (clickable if functional, not highlighted)
448
- h2(Text2, {
449
- key: `arrow-${actualIndex}`,
450
- color: "white"
451
- }, arrowPrefix),
452
- // Selection marker space (always same width, not highlighted)
453
- h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
454
- // Custom rendered content
455
- renderItem(item, isSelected, displayIndex)
456
- )
457
- });
458
- } else {
496
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
497
+ const clipped = needsBar ? { ...item, name: clipNameForScrollbar(item.name, true) } : item;
498
+ const row = h2(
499
+ Box2,
500
+ { flexDirection: "row" },
501
+ h2(Text2, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
502
+ h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
503
+ renderItem(clipped, isSelected, displayIndex)
504
+ );
459
505
  return h2(ScreenRow, {
460
506
  key: `item-${actualIndex}`,
461
- children: itemRenderer(item, isSelected, displayIndex, actualIndex)
507
+ children: appendBar(row, displayIndex)
462
508
  });
463
509
  }
510
+ return h2(ScreenRow, {
511
+ key: `item-${actualIndex}`,
512
+ children: appendBar(
513
+ itemRenderer(item, isSelected, displayIndex, actualIndex),
514
+ displayIndex
515
+ )
516
+ });
464
517
  })
465
518
  );
466
519
  }
@@ -468,6 +521,7 @@ var h2;
468
521
  var init_list_components = __esm({
469
522
  "src/screen/list-components.js"() {
470
523
  init_components();
524
+ init_scrollbar();
471
525
  h2 = createElement;
472
526
  }
473
527
  });
@@ -962,21 +1016,6 @@ function wrapTextLines(text, cols) {
962
1016
  }
963
1017
  return out;
964
1018
  }
965
- function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
966
- const view = Math.max(1, Math.floor(viewportRows));
967
- const total = Math.max(0, Math.floor(totalLines));
968
- if (total <= view) return null;
969
- const maxScroll = total - view;
970
- const thumbSize = Math.max(1, Math.round(view / total * view));
971
- const travel = Math.max(0, view - thumbSize);
972
- const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
973
- const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
974
- const glyphs = [];
975
- for (let i = 0; i < view; i++) {
976
- glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
977
- }
978
- return glyphs;
979
- }
980
1019
  function padEndVisible(s, w) {
981
1020
  const t = String(s ?? "");
982
1021
  if (t.length >= w) return t.slice(0, w);
@@ -1077,22 +1116,17 @@ function ScrollableText({
1077
1116
  ...rowNodes
1078
1117
  );
1079
1118
  }
1080
- var h5, BAR_THUMB, BAR_TRACK, SCROLL_KEYS;
1119
+ var h5, SCROLL_KEYS;
1081
1120
  var init_scrollable_text = __esm({
1082
1121
  "src/screen/scrollable-text.js"() {
1083
1122
  init_components();
1123
+ init_scrollbar();
1124
+ init_scrollbar();
1084
1125
  h5 = createElement2;
1085
- BAR_THUMB = "#";
1086
- BAR_TRACK = "|";
1087
1126
  SCROLL_KEYS = [
1088
1127
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1089
1128
  { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1090
- { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1091
- { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1092
- { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1093
- { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1094
- { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1095
- { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1129
+ ...PAGE_SCROLL_KEY_BINDINGS
1096
1130
  ];
1097
1131
  }
1098
1132
  });
@@ -1245,6 +1279,7 @@ var init_screen = __esm({
1245
1279
  init_components();
1246
1280
  init_ui_elements();
1247
1281
  init_scrollable_text();
1282
+ init_scrollbar();
1248
1283
  init_key_bindings();
1249
1284
  init_utils();
1250
1285
  init_footer_builder();
@@ -7018,12 +7053,13 @@ function tasksSchemaSpec(queueName = "tasks") {
7018
7053
  }
7019
7054
  };
7020
7055
  }
7021
- function taskHistoryInsertFromQueueRow(row, overrides) {
7056
+ function taskHistoryInsertFromQueueRow(row, overrides = {}) {
7022
7057
  const { id, ...snapshot } = row;
7023
- void id;
7058
+ const opid = overrides.opid !== void 0 ? overrides.opid : snapshot.opid != null && String(snapshot.opid).trim() !== "" ? snapshot.opid : id;
7024
7059
  return {
7025
7060
  ...snapshot,
7026
- ...overrides
7061
+ ...overrides,
7062
+ opid
7027
7063
  };
7028
7064
  }
7029
7065
  async function ensureTaskTables(context, options = {}) {
@@ -8286,69 +8322,6 @@ var TaskStopRunner = class extends AbstractTask {
8286
8322
  }
8287
8323
  };
8288
8324
 
8289
- // src/tasks/coreTasks/TaskGetLogs.js
8290
- var TaskGetLogs = class extends AbstractTask {
8291
- /**
8292
- * @param {object} context
8293
- * @param {Record<string, unknown>} [overrides]
8294
- * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
8295
- */
8296
- static async resolveCustomParams(context, overrides = {}) {
8297
- const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
8298
- source: "string",
8299
- resource: "string",
8300
- tail: "number default 100",
8301
- afterTs: "string"
8302
- }, overrides);
8303
- const source = typeof merged.source === "string" ? merged.source.trim() : "";
8304
- const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
8305
- if (!source) throw new ParamError('getLogs: param "source" is required');
8306
- if (!resource) throw new ParamError('getLogs: param "resource" is required');
8307
- let tail = Number(merged.tail);
8308
- if (!Number.isFinite(tail) || tail < 1) tail = 100;
8309
- tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
8310
- const out = { source, resource, tail };
8311
- if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
8312
- out.afterTs = merged.afterTs.trim();
8313
- }
8314
- return out;
8315
- }
8316
- /**
8317
- * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
8318
- * @returns {Promise<{ success: boolean, results: unknown }>}
8319
- */
8320
- async run(_reportProgress) {
8321
- const p = this.task.params ?? {};
8322
- const source = String(p.source ?? "").trim();
8323
- const resource = String(p.resource ?? "").trim();
8324
- const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
8325
- const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
8326
- if (!source || !resource) {
8327
- return {
8328
- success: false,
8329
- results: { error: 'getLogs requires params "source" and "resource"' }
8330
- };
8331
- }
8332
- try {
8333
- const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
8334
- source,
8335
- resource,
8336
- tail,
8337
- afterTs
8338
- });
8339
- return {
8340
- success: true,
8341
- results: { records, latestTs, source, resource }
8342
- };
8343
- } catch (e) {
8344
- return {
8345
- success: false,
8346
- results: { error: e?.message ?? String(e) }
8347
- };
8348
- }
8349
- }
8350
- };
8351
-
8352
8325
  // src/tasks/runtimeParams.js
8353
8326
  var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
8354
8327
  var LOGGER_RUNTIME_KEYS = [
@@ -8362,7 +8335,77 @@ var LOGGER_RUNTIME_KEYS = [
8362
8335
  "progressWithTimes",
8363
8336
  "progressThrottleMs"
8364
8337
  ];
8365
- var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
8338
+ var DEFAULT_RUNTIME_PARAM_SPECS = [
8339
+ {
8340
+ key: "maxParallel",
8341
+ type: "number",
8342
+ label: "Max parallel",
8343
+ description: "Worker-lane concurrency (how many tasks claim at once)"
8344
+ },
8345
+ {
8346
+ key: "pollMs",
8347
+ type: "number",
8348
+ label: "Poll ms",
8349
+ description: "Idle poll interval between claim attempts"
8350
+ },
8351
+ {
8352
+ key: "claimJitterMs",
8353
+ type: "number",
8354
+ label: "Claim jitter ms",
8355
+ description: "Random delay before worker claims (0 = off)"
8356
+ },
8357
+ {
8358
+ key: "scanLimit",
8359
+ type: "number",
8360
+ label: "Scan limit",
8361
+ description: "Max idle rows scanned per claim attempt"
8362
+ }
8363
+ ];
8364
+ function mergeRuntimeParamSpecs(extra) {
8365
+ const byKey = new Map(DEFAULT_RUNTIME_PARAM_SPECS.map((s) => [s.key, { ...s }]));
8366
+ if (Array.isArray(extra)) {
8367
+ for (const raw of extra) {
8368
+ if (!raw || typeof raw !== "object") continue;
8369
+ const key = String(raw.key ?? "").trim();
8370
+ if (!key) continue;
8371
+ const prev = byKey.get(key) ?? {};
8372
+ const type = ["number", "boolean", "string"].includes(raw.type) ? raw.type : prev.type ?? "string";
8373
+ byKey.set(key, {
8374
+ key,
8375
+ type,
8376
+ label: String(raw.label ?? prev.label ?? key),
8377
+ description: raw.description != null ? String(raw.description) : prev.description != null ? String(prev.description) : void 0
8378
+ });
8379
+ }
8380
+ }
8381
+ return Array.from(byKey.values());
8382
+ }
8383
+ function runtimeValuesForSpecs(context, specs = DEFAULT_RUNTIME_PARAM_SPECS) {
8384
+ const rt = ensureTasksRuntime(context);
8385
+ const out = {};
8386
+ for (const s of specs) {
8387
+ if (rt[s.key] !== void 0) out[s.key] = rt[s.key];
8388
+ }
8389
+ return out;
8390
+ }
8391
+ function runtimeParamSpecsFromMetadata(metadata) {
8392
+ const meta = metadata && typeof metadata === "object" ? metadata : null;
8393
+ const raw = meta?.runtimeParams;
8394
+ if (!Array.isArray(raw) || raw.length === 0) {
8395
+ return mergeRuntimeParamSpecs();
8396
+ }
8397
+ return mergeRuntimeParamSpecs(raw);
8398
+ }
8399
+ var CONTROL_LANE_TASK_NAMES = [
8400
+ "stopRunner",
8401
+ "stop",
8402
+ "pauseRunner",
8403
+ "pause",
8404
+ "unpauseRunner",
8405
+ "unpause",
8406
+ "setRuntimeParam",
8407
+ "setRunnerParam"
8408
+ ];
8366
8409
  function controlLaneTaskNames(extra) {
8367
8410
  const names = [...CONTROL_LANE_TASK_NAMES];
8368
8411
  if (extra == null || extra === "") return names;
@@ -8425,6 +8468,7 @@ function ensureTasksRuntime(context, seed = {}) {
8425
8468
  if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
8426
8469
  if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
8427
8470
  if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
8471
+ if (rt.paused === void 0) rt.paused = seed.paused === true;
8428
8472
  return rt;
8429
8473
  }
8430
8474
  async function applyRuntimeParam(context, key, value) {
@@ -8448,12 +8492,15 @@ async function applyRuntimeParam(context, key, value) {
8448
8492
  const reg = context.servicesRegistry;
8449
8493
  if (reg?.rowId && reg?.registryTable) {
8450
8494
  try {
8451
- const loopSnapshot = {};
8495
+ const specs = Array.isArray(context.tasksRuntimeParamSpecs) ? context.tasksRuntimeParamSpecs : DEFAULT_RUNTIME_PARAM_SPECS;
8496
+ const runtimeSnapshot = runtimeValuesForSpecs(context, specs);
8452
8497
  for (const lk of LOOP_RUNTIME_KEYS) {
8453
- if (runtime[lk] !== void 0) loopSnapshot[lk] = runtime[lk];
8498
+ if (runtime[lk] !== void 0 && runtimeSnapshot[lk] === void 0) {
8499
+ runtimeSnapshot[lk] = runtime[lk];
8500
+ }
8454
8501
  }
8455
8502
  await updateServicesRegistryMetadata(context, reg, {
8456
- runtime: loopSnapshot,
8503
+ runtime: runtimeSnapshot,
8457
8504
  runtimeUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
8458
8505
  });
8459
8506
  applied.push("servicesRegistry");
@@ -8489,6 +8536,140 @@ function readLoopRuntime(context) {
8489
8536
  };
8490
8537
  }
8491
8538
 
8539
+ // src/tasks/coreTasks/TaskPauseRunner.js
8540
+ async function applyRunnerPaused(context, paused) {
8541
+ const runtime = ensureTasksRuntime(context);
8542
+ const was = runtime.paused === true;
8543
+ runtime.paused = paused === true;
8544
+ const registry = context.servicesRegistry;
8545
+ if (registry && typeof registry === "object") {
8546
+ await updateServicesRegistryMetadata(context, registry, {
8547
+ paused: runtime.paused,
8548
+ pausedAt: runtime.paused ? (/* @__PURE__ */ new Date()).toISOString() : null
8549
+ });
8550
+ }
8551
+ 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).";
8552
+ context.logger?.warn?.(
8553
+ runtime.paused ? `[TaskPauseRunner] ${message}` : `[TaskUnpauseRunner] ${message}`
8554
+ );
8555
+ return {
8556
+ success: true,
8557
+ results: {
8558
+ paused: runtime.paused,
8559
+ message
8560
+ }
8561
+ };
8562
+ }
8563
+ var TaskPauseRunner = class extends AbstractTask {
8564
+ static taskName = "pauseRunner";
8565
+ static description = "Pause a runner: finish in-flight tasks, claim no new worker tasks until unpaused";
8566
+ static aliases = ["pause"];
8567
+ static defaultWaitForResult = true;
8568
+ /**
8569
+ * @param {object} context
8570
+ * @param {Record<string, unknown>} [overrides]
8571
+ * @returns {Promise<object>}
8572
+ */
8573
+ static async resolveParams(context, overrides = {}) {
8574
+ const main = await super.resolveParams(context, overrides);
8575
+ if (!main.serviceName) {
8576
+ throw new ParamError(
8577
+ "pause/pauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
8578
+ );
8579
+ }
8580
+ return main;
8581
+ }
8582
+ async run() {
8583
+ return applyRunnerPaused(this.context, true);
8584
+ }
8585
+ };
8586
+ var TaskUnpauseRunner = class extends AbstractTask {
8587
+ static taskName = "unpauseRunner";
8588
+ static description = "Unpause a runner: resume claiming worker tasks";
8589
+ static aliases = ["unpause"];
8590
+ static defaultWaitForResult = true;
8591
+ /**
8592
+ * @param {object} context
8593
+ * @param {Record<string, unknown>} [overrides]
8594
+ * @returns {Promise<object>}
8595
+ */
8596
+ static async resolveParams(context, overrides = {}) {
8597
+ const main = await super.resolveParams(context, overrides);
8598
+ if (!main.serviceName) {
8599
+ throw new ParamError(
8600
+ "unpause/unpauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
8601
+ );
8602
+ }
8603
+ return main;
8604
+ }
8605
+ async run() {
8606
+ return applyRunnerPaused(this.context, false);
8607
+ }
8608
+ };
8609
+
8610
+ // src/tasks/coreTasks/TaskGetLogs.js
8611
+ var TaskGetLogs = class extends AbstractTask {
8612
+ /**
8613
+ * @param {object} context
8614
+ * @param {Record<string, unknown>} [overrides]
8615
+ * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
8616
+ */
8617
+ static async resolveCustomParams(context, overrides = {}) {
8618
+ const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
8619
+ source: "string",
8620
+ resource: "string",
8621
+ tail: "number default 100",
8622
+ afterTs: "string"
8623
+ }, overrides);
8624
+ const source = typeof merged.source === "string" ? merged.source.trim() : "";
8625
+ const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
8626
+ if (!source) throw new ParamError('getLogs: param "source" is required');
8627
+ if (!resource) throw new ParamError('getLogs: param "resource" is required');
8628
+ let tail = Number(merged.tail);
8629
+ if (!Number.isFinite(tail) || tail < 1) tail = 100;
8630
+ tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
8631
+ const out = { source, resource, tail };
8632
+ if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
8633
+ out.afterTs = merged.afterTs.trim();
8634
+ }
8635
+ return out;
8636
+ }
8637
+ /**
8638
+ * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
8639
+ * @returns {Promise<{ success: boolean, results: unknown }>}
8640
+ */
8641
+ async run(_reportProgress) {
8642
+ const p = this.task.params ?? {};
8643
+ const source = String(p.source ?? "").trim();
8644
+ const resource = String(p.resource ?? "").trim();
8645
+ const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
8646
+ const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
8647
+ if (!source || !resource) {
8648
+ return {
8649
+ success: false,
8650
+ results: { error: 'getLogs requires params "source" and "resource"' }
8651
+ };
8652
+ }
8653
+ try {
8654
+ const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
8655
+ source,
8656
+ resource,
8657
+ tail,
8658
+ afterTs
8659
+ });
8660
+ return {
8661
+ success: true,
8662
+ results: { records, latestTs, source, resource }
8663
+ };
8664
+ } catch (e) {
8665
+ return {
8666
+ success: false,
8667
+ results: { error: e?.message ?? String(e) }
8668
+ };
8669
+ }
8670
+ }
8671
+ };
8672
+
8492
8673
  // src/tasks/coreTasks/TaskSetRuntimeParam.js
8493
8674
  var TaskSetRuntimeParam = class extends AbstractTask {
8494
8675
  static defaultWaitForResult = true;
@@ -8644,7 +8825,7 @@ var TasksRegistry = class _TasksRegistry {
8644
8825
  * @returns {TasksRegistry}
8645
8826
  */
8646
8827
  static withCoreTasks() {
8647
- 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);
8828
+ 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);
8648
8829
  }
8649
8830
  /**
8650
8831
  * Register a single task class under a name. Overwrites any previous entry.
@@ -8747,6 +8928,10 @@ var SERVICE_TASK_NAMES = [
8747
8928
  "ping",
8748
8929
  "stop",
8749
8930
  "stopRunner",
8931
+ "pause",
8932
+ "pauseRunner",
8933
+ "unpause",
8934
+ "unpauseRunner",
8750
8935
  "shellCommand",
8751
8936
  "systemInfo",
8752
8937
  "info",
@@ -9208,17 +9393,25 @@ async function runTasksLoop(context, options) {
9208
9393
  if (hbGroup) {
9209
9394
  const hbIntervalMs = options.runnerHeartbeatIntervalMs ?? 1e4;
9210
9395
  const staleMs = options.runnerHeartbeatStaleMs ?? 45e3;
9211
- const loop0 = readLoopRuntime(context);
9396
+ const runtimeParamSpecs = mergeRuntimeParamSpecs(options.runnerRuntimeParams);
9397
+ context.tasksRuntimeParamSpecs = runtimeParamSpecs;
9212
9398
  const defaultMeta = {
9213
9399
  component: "tasks-runner",
9214
9400
  allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
9215
- runtime: {
9216
- maxParallel: loop0.maxParallel,
9217
- pollMs: loop0.pollMs,
9218
- claimJitterMs: loop0.claimJitterMs,
9219
- scanLimit: loop0.scanLimit
9220
- }
9401
+ paused: false,
9402
+ runtimeParams: runtimeParamSpecs,
9403
+ runtime: runtimeValuesForSpecs(context, runtimeParamSpecs)
9221
9404
  };
9405
+ const metadata = {
9406
+ ...defaultMeta,
9407
+ ...options.runnerMetadata && typeof options.runnerMetadata === "object" ? options.runnerMetadata : {}
9408
+ };
9409
+ if (!Array.isArray(metadata.runtimeParams) || metadata.runtimeParams.length === 0) {
9410
+ metadata.runtimeParams = defaultMeta.runtimeParams;
9411
+ }
9412
+ if (!metadata.runtime || typeof metadata.runtime !== "object") {
9413
+ metadata.runtime = defaultMeta.runtime;
9414
+ }
9222
9415
  registryReg = await registerInServicesRegistry(context, {
9223
9416
  queueName,
9224
9417
  target,
@@ -9228,7 +9421,7 @@ async function runTasksLoop(context, options) {
9228
9421
  staleMs,
9229
9422
  groupMaxInstances: options.runnerGroupMaxInstances,
9230
9423
  enforceMaxInstances: options.runnerEnforceMaxInstances ?? true,
9231
- metadata: options.runnerMetadata ?? defaultMeta
9424
+ metadata
9232
9425
  });
9233
9426
  context.servicesRegistry = registryReg;
9234
9427
  runnerIdentity = {
@@ -9278,28 +9471,38 @@ async function runTasksLoop(context, options) {
9278
9471
  if (claimJitterMs > 0) {
9279
9472
  await sleepMs(Math.floor(Math.random() * (claimJitterMs + 1)));
9280
9473
  }
9281
- while (runningPromises.size < maxParallel) {
9282
- const claimed = await claimNextRunnableTask(
9283
- context,
9284
- tasksTable,
9285
- target,
9286
- registry,
9287
- scanLimit,
9288
- allowedTasks,
9289
- runnerIdentity
9290
- );
9291
- if (!claimed) break;
9292
- const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
9293
- if (outcome.stopRunnerRequested && !stopRequested) {
9294
- stopRequested = true;
9295
- stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
9296
- context.tasksRunnerStop = true;
9297
- await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
9298
- }
9299
- }).finally(() => {
9300
- runningPromises.delete(p);
9301
- });
9302
- runningPromises.add(p);
9474
+ const paused = context.tasksRuntime?.paused === true;
9475
+ if (!paused) {
9476
+ while (runningPromises.size < maxParallel) {
9477
+ const claimed = await claimNextRunnableTask(
9478
+ context,
9479
+ tasksTable,
9480
+ target,
9481
+ registry,
9482
+ scanLimit,
9483
+ allowedTasks,
9484
+ runnerIdentity
9485
+ );
9486
+ if (!claimed) break;
9487
+ const p = executeClaimedTask(
9488
+ context,
9489
+ tasksTable,
9490
+ historyTable,
9491
+ claimed,
9492
+ registry,
9493
+ runningTaskInstances
9494
+ ).then(async (outcome) => {
9495
+ if (outcome.stopRunnerRequested && !stopRequested) {
9496
+ stopRequested = true;
9497
+ stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
9498
+ context.tasksRunnerStop = true;
9499
+ await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
9500
+ }
9501
+ }).finally(() => {
9502
+ runningPromises.delete(p);
9503
+ });
9504
+ runningPromises.add(p);
9505
+ }
9303
9506
  }
9304
9507
  const wakePromises = [...runningPromises];
9305
9508
  if (runningControlPromise) {
@@ -9353,16 +9556,26 @@ async function waitForTaskResult(context, taskId, options = {}) {
9353
9556
  const pollMs = options.pollMs ?? 500;
9354
9557
  const { tasksTable, historyTable } = queueToTableNames(queueName);
9355
9558
  const deadline = Date.now() + timeoutMs;
9356
- const waitStartedAt = /* @__PURE__ */ new Date();
9357
- let cachedNameOpid = null;
9358
- async function historySinceWait(name, opid) {
9359
- let q = db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
9559
+ const waitStartedAt = new Date(Date.now() - 5e3);
9560
+ let cachedNameOpid = options.name != null && String(options.name).trim() !== "" ? {
9561
+ name: String(options.name).trim(),
9562
+ opid: options.opid !== void 0 ? options.opid : null
9563
+ } : null;
9564
+ async function findHistory(name, opid) {
9565
+ const base = () => db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
9566
+ if (opid != null && String(opid).trim() !== "") {
9567
+ const byOpid = await base().where({ opid }).orderBy("completed_at", "desc").first();
9568
+ if (byOpid) return byOpid;
9569
+ }
9570
+ const byQueueId = await base().where({ opid: taskId }).orderBy("completed_at", "desc").first();
9571
+ if (byQueueId) return byQueueId;
9572
+ const byQueueIdAny = await db(historyTable).where({ name, opid: taskId }).orderBy("completed_at", "desc").first();
9573
+ if (byQueueIdAny) return byQueueIdAny;
9360
9574
  if (opid == null || opid === "") {
9361
- q = q.whereNull("opid");
9362
- } else {
9363
- q = q.where({ opid });
9575
+ const byNull = await base().whereNull("opid").orderBy("completed_at", "desc").first();
9576
+ if (byNull) return byNull;
9364
9577
  }
9365
- return await q.orderBy("completed_at", "desc").first();
9578
+ return void 0;
9366
9579
  }
9367
9580
  while (Date.now() <= deadline) {
9368
9581
  const legacy = await db(historyTable).where({ id: taskId }).orderBy("created_at", "desc").first();
@@ -9372,18 +9585,17 @@ async function waitForTaskResult(context, taskId, options = {}) {
9372
9585
  const pending = await db(tasksTable).where({ id: taskId }).first();
9373
9586
  if (pending) {
9374
9587
  cachedNameOpid = { name: pending.name, opid: pending.opid };
9375
- const done = await historySinceWait(pending.name, pending.opid);
9376
- if (done) {
9377
- return done;
9378
- }
9379
- } else if (cachedNameOpid) {
9380
- const done = await historySinceWait(cachedNameOpid.name, cachedNameOpid.opid);
9588
+ }
9589
+ if (cachedNameOpid) {
9590
+ const done = await findHistory(cachedNameOpid.name, cachedNameOpid.opid);
9381
9591
  if (done) {
9382
9592
  return done;
9383
9593
  }
9384
- return null;
9385
9594
  } else {
9386
- return null;
9595
+ const byQueueId = await db(historyTable).where({ opid: taskId }).orderBy("completed_at", "desc").first();
9596
+ if (byQueueId) {
9597
+ return byQueueId;
9598
+ }
9387
9599
  }
9388
9600
  await sleepMs(pollMs);
9389
9601
  }
@@ -9525,7 +9737,10 @@ export {
9525
9737
  AbstractTask,
9526
9738
  Args,
9527
9739
  Aws,
9740
+ BAR_THUMB,
9741
+ BAR_TRACK,
9528
9742
  Box5 as Box,
9743
+ DEFAULT_RUNTIME_PARAM_SPECS,
9529
9744
  Db,
9530
9745
  Divider,
9531
9746
  FileDatabase,
@@ -9539,6 +9754,7 @@ export {
9539
9754
  ListItem,
9540
9755
  MultiColumnListComponent,
9541
9756
  MultiColumnListWithPreviewComponent,
9757
+ PAGE_SCROLL_KEY_BINDINGS,
9542
9758
  Params,
9543
9759
  REMOTE_CLI_REL,
9544
9760
  React2 as React,
@@ -9552,6 +9768,7 @@ export {
9552
9768
  ScreenTitle,
9553
9769
  ScrollableText,
9554
9770
  TaskGetLogs,
9771
+ TaskPauseRunner,
9555
9772
  TaskPing,
9556
9773
  TaskSampleProcess,
9557
9774
  TaskSetRuntimeParam,
@@ -9559,6 +9776,7 @@ export {
9559
9776
  TaskStopRunner,
9560
9777
  TaskSumAB,
9561
9778
  TaskSystemInfo,
9779
+ TaskUnpauseRunner,
9562
9780
  TasksManager,
9563
9781
  TasksRegistry,
9564
9782
  Text6 as Text,
@@ -9566,6 +9784,7 @@ export {
9566
9784
  activateRelease,
9567
9785
  appendDeployLog,
9568
9786
  appendTaskIpcLog,
9787
+ applyRunnerPaused,
9569
9788
  applyRuntimeParam,
9570
9789
  applyRuntimePatch,
9571
9790
  bindingIdentity,
@@ -9624,6 +9843,7 @@ export {
9624
9843
  matchesParsedPattern,
9625
9844
  memo,
9626
9845
  mergeAllowedTasksWithServiceTasks,
9846
+ mergeRuntimeParamSpecs,
9627
9847
  nextTimeMatch,
9628
9848
  normalizeAllowedTasks,
9629
9849
  npmEnv,
@@ -9660,6 +9880,8 @@ export {
9660
9880
  runRemoteStatus,
9661
9881
  runShell,
9662
9882
  runTasksLoop,
9883
+ runtimeParamSpecsFromMetadata,
9884
+ runtimeValuesForSpecs,
9663
9885
  scrollbarGlyphs,
9664
9886
  scrubEnvContent,
9665
9887
  servicePaths,