@nmakarov/cli-toolkit 0.47.0 → 0.50.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
@@ -1081,6 +1081,8 @@ __export(src_exports, {
1081
1081
  FooterPresets: () => FooterPresets,
1082
1082
  GridCell: () => GridCell,
1083
1083
  InputField: () => InputField,
1084
+ LOGGER_RUNTIME_KEYS: () => LOGGER_RUNTIME_KEYS,
1085
+ LOOP_RUNTIME_KEYS: () => LOOP_RUNTIME_KEYS,
1084
1086
  ListComponent: () => ListComponent,
1085
1087
  ListItem: () => ListItem,
1086
1088
  MultiColumnListComponent: () => MultiColumnListComponent,
@@ -1099,6 +1101,7 @@ __export(src_exports, {
1099
1101
  TaskGetLogs: () => TaskGetLogs,
1100
1102
  TaskPing: () => TaskPing,
1101
1103
  TaskSampleProcess: () => TaskSampleProcess,
1104
+ TaskSetRuntimeParam: () => TaskSetRuntimeParam,
1102
1105
  TaskShellCommand: () => TaskShellCommand,
1103
1106
  TaskStopRunner: () => TaskStopRunner,
1104
1107
  TaskSumAB: () => TaskSumAB,
@@ -1110,12 +1113,16 @@ __export(src_exports, {
1110
1113
  activateRelease: () => activateRelease,
1111
1114
  appendDeployLog: () => appendDeployLog,
1112
1115
  appendTaskIpcLog: () => appendTaskIpcLog,
1116
+ applyRuntimeParam: () => applyRuntimeParam,
1117
+ applyRuntimePatch: () => applyRuntimePatch,
1113
1118
  bootstrapHost: () => bootstrapHost,
1114
1119
  buildBreadcrumb: () => buildBreadcrumb,
1115
1120
  buildDetailBreadcrumb: () => buildDetailBreadcrumb,
1116
1121
  buildFooter: () => buildFooter,
1117
1122
  bumpPatchVersion: () => bumpPatchVersion,
1118
1123
  cloneRepo: () => cloneRepo,
1124
+ coerceRuntimeValue: () => coerceRuntimeValue,
1125
+ controlLaneTaskNames: () => controlLaneTaskNames,
1119
1126
  convertPattern: () => convertPattern,
1120
1127
  createRelease: () => createRelease,
1121
1128
  defaultFileSynopsisFunction: () => defaultFileSynopsisFunction,
@@ -1137,11 +1144,13 @@ __export(src_exports, {
1137
1144
  ensureSchemaEverywhere: () => ensureSchemaEverywhere,
1138
1145
  ensureTable: () => ensureTable,
1139
1146
  ensureTaskTables: () => ensureTaskTables,
1147
+ ensureTasksRuntime: () => ensureTasksRuntime,
1140
1148
  flushTaskIpcLogs: () => flushTaskIpcLogs,
1141
1149
  getArgsInstance: () => getArgsInstance,
1142
1150
  h: () => import_react5.createElement,
1143
1151
  initServiceStructure: () => initServiceStructure,
1144
1152
  installDeps: () => installDeps,
1153
+ installOperatorShell: () => installOperatorShell,
1145
1154
  ipcFileLogsTableNameForSourceResource: () => ipcFileLogsTableNameForSourceResource,
1146
1155
  joiEdateType: () => joiEdateType,
1147
1156
  joiStringArrayType: () => joiStringArrayType,
@@ -1167,6 +1176,7 @@ __export(src_exports, {
1167
1176
  pullRepo: () => pullRepo,
1168
1177
  queueToTableNames: () => queueToTableNames,
1169
1178
  readCurrentRelease: () => readCurrentRelease,
1179
+ readLoopRuntime: () => readLoopRuntime,
1170
1180
  readReleaseBuildInfo: () => readReleaseBuildInfo,
1171
1181
  readTaskIpcLogsSnapshot: () => readTaskIpcLogsSnapshot,
1172
1182
  registerInServicesRegistry: () => registerInServicesRegistry,
@@ -5940,10 +5950,9 @@ function buildEcosystemConfig(service, paths) {
5940
5950
  const outLog = (0, import_node_path8.join)(paths.logs, `${pm2.appName}.out.log`);
5941
5951
  const errLog = (0, import_node_path8.join)(paths.logs, `${pm2.appName}.err.log`);
5942
5952
  return `/**
5943
- * pm2 ecosystem for ${service.name} \u2014 seeded by cli-toolkit deploy (init).
5944
- *
5945
- * cwd points at the \`current\` symlink (created on first deploy).
5946
- * Tweak \`args\` here, then: pm2 reload ${paths.ecosystem} --update-env
5953
+ * pm2 ecosystem for ${service.name} \u2014 written by cli-toolkit deploy from the
5954
+ * service manifest (init/deploy). Do not hand-edit; change pm2.* in the
5955
+ * manifest and redeploy. cwd \u2192 \`current\` symlink.
5947
5956
  */
5948
5957
  module.exports = {
5949
5958
  apps: [
@@ -5951,7 +5960,7 @@ module.exports = {
5951
5960
  name: "${pm2.appName}",
5952
5961
  script: "${pm2.script}",
5953
5962
  cwd: "${paths.current}",
5954
- args: "${pm2.args}",
5963
+ args: "${pm2.args ?? ""}",
5955
5964
  instances: 1,
5956
5965
  exec_mode: "fork",
5957
5966
  autorestart: true,
@@ -5990,17 +5999,17 @@ async function initServiceStructure(service, options = {}) {
5990
5999
  if (!dryRun) await (0, import_promises11.mkdir)(dir, { recursive: true });
5991
6000
  created.push(dir);
5992
6001
  }
5993
- let ecosystemCreated = false;
5994
- if (await pathExists4(paths.ecosystem)) {
5995
- skipped.push(paths.ecosystem);
6002
+ const ecosystemExisted = await pathExists4(paths.ecosystem);
6003
+ if (!dryRun) {
6004
+ await (0, import_promises11.writeFile)(paths.ecosystem, buildEcosystemConfig(service, paths), { mode: 420 });
6005
+ }
6006
+ if (ecosystemExisted) {
6007
+ logger.info(`synced ${paths.ecosystem} from manifest`);
5996
6008
  } else {
5997
- if (!dryRun) {
5998
- await (0, import_promises11.writeFile)(paths.ecosystem, buildEcosystemConfig(service, paths), { mode: 420 });
5999
- }
6000
- ecosystemCreated = true;
6001
6009
  created.push(paths.ecosystem);
6010
+ logger.info(`seeded ${paths.ecosystem}`);
6002
6011
  }
6003
- const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] init-structure service=${service.name} dryRun=${dryRun} created=${created.length} skipped=${skipped.length}
6012
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] init-structure service=${service.name} dryRun=${dryRun} created=${created.length} skipped=${skipped.length} ecosystem=synced
6004
6013
  `;
6005
6014
  if (!dryRun) {
6006
6015
  await (0, import_promises11.mkdir)(paths.logs, { recursive: true });
@@ -6009,8 +6018,7 @@ async function initServiceStructure(service, options = {}) {
6009
6018
  logger.info(`service=${service.name} appsRoot=${paths.root}`);
6010
6019
  logger.info(`created: ${created.length ? created.join(", ") : "(none)"}`);
6011
6020
  logger.info(`already present: ${skipped.length ? skipped.join(", ") : "(none)"}`);
6012
- if (ecosystemCreated) logger.info(`seeded ${paths.ecosystem}`);
6013
- return { paths, created, skipped, ecosystemCreated };
6021
+ return { paths, created, skipped, ecosystemCreated: !ecosystemExisted, ecosystemSynced: true };
6014
6022
  }
6015
6023
 
6016
6024
  // src/deploy/bootstrap-host.js
@@ -6107,11 +6115,55 @@ async function installLogrotate(service, options = {}) {
6107
6115
  await runShell(`sudo cp '${tmp}' '${conf}'`, { logger });
6108
6116
  logger.info(`logrotate config written: ${conf}`);
6109
6117
  }
6118
+ var OPERATOR_SHELL_BEGIN = "# >>> mlsfarm-operator >>>";
6119
+ var OPERATOR_SHELL_END = "# <<< mlsfarm-operator <<<";
6120
+ async function installOperatorShell(service, options = {}) {
6121
+ const { user = "ubuntu", dryRun = false, logger = console } = options;
6122
+ const appsRoot = service.appsRoot;
6123
+ if (!appsRoot) {
6124
+ logger.warn("installOperatorShell: service.appsRoot missing \u2014 skipped");
6125
+ return;
6126
+ }
6127
+ const home = user === "ubuntu" || user === process.env.USER ? (0, import_node_path9.join)((0, import_node_os2.homedir)(), ".bashrc") : `/home/${user}/.bashrc`;
6128
+ const currentDir = (0, import_node_path9.join)(appsRoot, "current");
6129
+ const block = [
6130
+ OPERATOR_SHELL_BEGIN,
6131
+ "# Managed by cli-toolkit deploy bootstrap \u2014 do not edit by hand.",
6132
+ "export ENV=production",
6133
+ `if [ -d "${currentDir}" ]; then`,
6134
+ ` cd "${currentDir}" || true`,
6135
+ "fi",
6136
+ OPERATOR_SHELL_END,
6137
+ ""
6138
+ ].join("\n");
6139
+ if (dryRun) {
6140
+ logger.info(`[dryRun] would update ${home} (ENV=production, cd ${currentDir})`);
6141
+ return;
6142
+ }
6143
+ let existing = "";
6144
+ if (await pathExists5(home)) {
6145
+ existing = await (0, import_promises12.readFile)(home, "utf8");
6146
+ }
6147
+ const begin = existing.indexOf(OPERATOR_SHELL_BEGIN);
6148
+ const end = existing.indexOf(OPERATOR_SHELL_END);
6149
+ let next;
6150
+ if (begin !== -1 && end !== -1 && end > begin) {
6151
+ const afterEnd = end + OPERATOR_SHELL_END.length;
6152
+ const rest = existing.slice(afterEnd).replace(/^\n/, "");
6153
+ next = `${existing.slice(0, begin).replace(/\s*$/, "\n")}${block}${rest}`;
6154
+ } else {
6155
+ next = `${existing.replace(/\s*$/, "\n")}
6156
+ ${block}`;
6157
+ }
6158
+ await (0, import_promises12.writeFile)(home, next, { mode: 420 });
6159
+ logger.info(`operator shell: ${home} \u2192 ENV=production, cd ${currentDir}`);
6160
+ }
6110
6161
  async function bootstrapHost(service, options = {}) {
6111
6162
  const { deployKey = service.deployKey, user = "ubuntu", dryRun = false, logger = console } = options;
6112
6163
  await ensurePm2Startup({ user, dryRun, logger });
6113
6164
  if (deployKey) await installDeployKey(deployKey, { dryRun, logger });
6114
6165
  await installLogrotate(service, { dryRun, logger });
6166
+ await installOperatorShell(service, { user, dryRun, logger });
6115
6167
  logger.info("bootstrap-host complete");
6116
6168
  }
6117
6169
 
@@ -7901,6 +7953,273 @@ var TaskGetLogs = class extends AbstractTask {
7901
7953
  }
7902
7954
  };
7903
7955
 
7956
+ // src/tasks/runtimeParams.js
7957
+ var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
7958
+ var LOGGER_RUNTIME_KEYS = [
7959
+ "levels",
7960
+ "silent",
7961
+ "showLevel",
7962
+ "timestamp",
7963
+ "mode",
7964
+ "route",
7965
+ "prefix",
7966
+ "progressWithTimes",
7967
+ "progressThrottleMs"
7968
+ ];
7969
+ var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
7970
+ function controlLaneTaskNames() {
7971
+ return [...CONTROL_LANE_TASK_NAMES];
7972
+ }
7973
+ function asPositiveInt(value, key, { min = 1 } = {}) {
7974
+ const n = Number(value);
7975
+ if (!Number.isFinite(n) || n < min) {
7976
+ throw new ParamError(
7977
+ `setRuntimeParam: ${key} must be a number >= ${min} (got ${JSON.stringify(value)})`
7978
+ );
7979
+ }
7980
+ return Math.floor(n);
7981
+ }
7982
+ function coerceRuntimeValue(key, value) {
7983
+ switch (key) {
7984
+ case "maxParallel":
7985
+ return asPositiveInt(value, key, { min: 1 });
7986
+ case "pollMs":
7987
+ return asPositiveInt(value, key, { min: 50 });
7988
+ case "claimJitterMs":
7989
+ return asPositiveInt(value, key, { min: 0 });
7990
+ case "scanLimit":
7991
+ return asPositiveInt(value, key, { min: 1 });
7992
+ case "silent":
7993
+ case "showLevel":
7994
+ case "timestamp":
7995
+ case "progressWithTimes":
7996
+ if (typeof value === "boolean") return value;
7997
+ if (value === "true" || value === "1") return true;
7998
+ if (value === "false" || value === "0") return false;
7999
+ throw new ParamError(
8000
+ `setRuntimeParam: ${key} must be boolean (got ${JSON.stringify(value)})`
8001
+ );
8002
+ case "progressThrottleMs":
8003
+ return asPositiveInt(value, key, { min: 0 });
8004
+ case "levels":
8005
+ case "mode":
8006
+ case "route":
8007
+ case "prefix":
8008
+ return value;
8009
+ default:
8010
+ return value;
8011
+ }
8012
+ }
8013
+ function ensureTasksRuntime(context, seed = {}) {
8014
+ if (!context.tasksRuntime || typeof context.tasksRuntime !== "object") {
8015
+ context.tasksRuntime = {};
8016
+ }
8017
+ const rt = context.tasksRuntime;
8018
+ if (rt.maxParallel === void 0) rt.maxParallel = seed.maxParallel ?? 32;
8019
+ if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
8020
+ if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
8021
+ if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
8022
+ return rt;
8023
+ }
8024
+ async function applyRuntimeParam(context, key, value) {
8025
+ const k = String(key ?? "").trim();
8026
+ if (!k) throw new ParamError("setRuntimeParam: key is required");
8027
+ const runtime = ensureTasksRuntime(context);
8028
+ const next = coerceRuntimeValue(k, value);
8029
+ const previous = runtime[k];
8030
+ const applied = [];
8031
+ runtime[k] = next;
8032
+ applied.push("tasksRuntime");
8033
+ if (LOGGER_RUNTIME_KEYS.includes(k) && context.logger?.configure) {
8034
+ context.logger.configure({ [k]: next });
8035
+ applied.push("logger");
8036
+ }
8037
+ const hook = context.tasksRuntimeOnParam;
8038
+ if (typeof hook === "function") {
8039
+ await hook(k, next, runtime, context);
8040
+ applied.push("onRuntimeParam");
8041
+ }
8042
+ const reg = context.servicesRegistry;
8043
+ if (reg?.rowId && reg?.registryTable) {
8044
+ try {
8045
+ const loopSnapshot = {};
8046
+ for (const lk of LOOP_RUNTIME_KEYS) {
8047
+ if (runtime[lk] !== void 0) loopSnapshot[lk] = runtime[lk];
8048
+ }
8049
+ await updateServicesRegistryMetadata(context, reg, {
8050
+ runtime: loopSnapshot,
8051
+ runtimeUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
8052
+ });
8053
+ applied.push("servicesRegistry");
8054
+ } catch (err) {
8055
+ context.logger?.warn?.(
8056
+ `[setRuntimeParam] registry metadata update failed: ${err?.message ?? String(err)}`
8057
+ );
8058
+ }
8059
+ }
8060
+ return { key: k, previous, next, applied };
8061
+ }
8062
+ async function applyRuntimePatch(context, patch) {
8063
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
8064
+ throw new ParamError("setRuntimeParam: patch must be a plain object");
8065
+ }
8066
+ const entries = Object.entries(patch);
8067
+ if (entries.length === 0) {
8068
+ throw new ParamError("setRuntimeParam: patch is empty");
8069
+ }
8070
+ const out = [];
8071
+ for (const [key, value] of entries) {
8072
+ out.push(await applyRuntimeParam(context, key, value));
8073
+ }
8074
+ return out;
8075
+ }
8076
+ function readLoopRuntime(context) {
8077
+ const rt = ensureTasksRuntime(context);
8078
+ return {
8079
+ maxParallel: Math.max(1, Number(rt.maxParallel) || 1),
8080
+ pollMs: Math.max(50, Number(rt.pollMs) || 1e3),
8081
+ claimJitterMs: Math.max(0, Number(rt.claimJitterMs) || 0),
8082
+ scanLimit: Math.max(1, Number(rt.scanLimit) || 100)
8083
+ };
8084
+ }
8085
+
8086
+ // src/tasks/coreTasks/TaskSetRuntimeParam.js
8087
+ var TaskSetRuntimeParam = class extends AbstractTask {
8088
+ static defaultWaitForResult = true;
8089
+ /**
8090
+ * @param {object} context
8091
+ * @param {Record<string, unknown>} [overrides]
8092
+ * @returns {Promise<object>}
8093
+ */
8094
+ static async resolveParams(context, overrides = {}) {
8095
+ const main = await super.resolveParams(context, overrides);
8096
+ if (!main.serviceName && !main.serviceGroup) {
8097
+ throw new ParamError(
8098
+ "setRuntimeParam requires --serviceName (one instance) or --serviceGroup (broadcast to alive instances)"
8099
+ );
8100
+ }
8101
+ return main;
8102
+ }
8103
+ /**
8104
+ * @param {object} context
8105
+ * @param {Record<string, unknown>} [overrides]
8106
+ * @returns {Promise<{ key?: string, value?: unknown, patch?: Record<string, unknown> }>}
8107
+ */
8108
+ static async resolveCustomParams(context, overrides = {}) {
8109
+ const merged = AbstractTask._mergeTypedParams(
8110
+ context,
8111
+ "task-set-runtime-param",
8112
+ {
8113
+ paramKey: "string",
8114
+ paramValue: "string",
8115
+ key: "string",
8116
+ value: "string"
8117
+ },
8118
+ overrides
8119
+ );
8120
+ if (merged.patch && typeof merged.patch === "object" && !Array.isArray(merged.patch)) {
8121
+ if (Object.keys(merged.patch).length === 0) {
8122
+ throw new ParamError("setRuntimeParam: patch is empty");
8123
+ }
8124
+ return { patch: { ...merged.patch } };
8125
+ }
8126
+ const key = merged.paramKey || merged.key;
8127
+ const value = merged.paramValue !== void 0 ? merged.paramValue : merged.value;
8128
+ if (!key) {
8129
+ throw new ParamError(
8130
+ `setRuntimeParam requires --paramKey/--paramValue, or --paramsJson '{"key":"maxParallel","value":16}' / '{"patch":{...}}'`
8131
+ );
8132
+ }
8133
+ let parsed = value;
8134
+ if (typeof value === "string") {
8135
+ const t = value.trim();
8136
+ if (t === "true") parsed = true;
8137
+ else if (t === "false") parsed = false;
8138
+ else if (t !== "" && !Number.isNaN(Number(t)) && /^-?\d+(\.\d+)?$/.test(t)) {
8139
+ parsed = Number(t);
8140
+ } else if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]") || t.startsWith('"') && t.endsWith('"')) {
8141
+ try {
8142
+ parsed = JSON.parse(t);
8143
+ } catch {
8144
+ parsed = value;
8145
+ }
8146
+ }
8147
+ }
8148
+ return { key: String(key), value: parsed };
8149
+ }
8150
+ /**
8151
+ * Enqueue one or many setRuntimeParam tasks. Prefer this over a bare
8152
+ * `enqueueTask` when broadcasting to a service group.
8153
+ *
8154
+ * @param {object} context
8155
+ * @param {Record<string, unknown>} [overrides]
8156
+ * @returns {Promise<{ ids: string[], targets: string[] }>}
8157
+ */
8158
+ static async enqueue(context, overrides = {}) {
8159
+ const payload = await this.resolveParams(context, { ...overrides, name: "setRuntimeParam" });
8160
+ const queueName = payload.queueName ?? "tasks";
8161
+ if (payload.serviceName) {
8162
+ const id = await enqueueTask(context, payload);
8163
+ return { ids: [id], targets: [payload.serviceName] };
8164
+ }
8165
+ const group = String(payload.serviceGroup || "").trim();
8166
+ if (!group) {
8167
+ throw new ParamError("setRuntimeParam.enqueue: serviceGroup required for broadcast");
8168
+ }
8169
+ const alive = await listServicesRegistry(context, {
8170
+ queueName,
8171
+ serviceGroup: group,
8172
+ staleMs: overrides.staleMs ?? 45e3
8173
+ });
8174
+ if (!alive.length) {
8175
+ throw new ParamError(
8176
+ `setRuntimeParam: no alive services in group="${group}" queue="${queueName}"`
8177
+ );
8178
+ }
8179
+ const ids = [];
8180
+ const targets = [];
8181
+ for (const reg of alive) {
8182
+ const id = await enqueueTask(context, {
8183
+ ...payload,
8184
+ serviceGroup: group,
8185
+ serviceName: reg.service_name,
8186
+ serverName: reg.server_name ?? null,
8187
+ instanceNumber: reg.instance_number ?? null
8188
+ });
8189
+ ids.push(id);
8190
+ targets.push(reg.service_name);
8191
+ }
8192
+ context.logger?.info?.(
8193
+ `[setRuntimeParam] broadcast to ${targets.length} instance(s) in group=${group}: ${targets.join(", ")}`
8194
+ );
8195
+ return { ids, targets };
8196
+ }
8197
+ /**
8198
+ * @returns {Promise<{ success: true, results: object }>}
8199
+ */
8200
+ async run() {
8201
+ const params = this.task?.params ?? {};
8202
+ let changes;
8203
+ if (params.patch && typeof params.patch === "object") {
8204
+ changes = await applyRuntimePatch(this.context, params.patch);
8205
+ } else {
8206
+ changes = [await applyRuntimeParam(this.context, params.key, params.value)];
8207
+ }
8208
+ const summary = changes.map((c) => `${c.key}: ${JSON.stringify(c.previous)} \u2192 ${JSON.stringify(c.next)}`);
8209
+ this.context.logger.warn?.(
8210
+ `[TaskSetRuntimeParam] applied on ${this.context.servicesRegistry?.serviceName ?? "runner"}: ${summary.join("; ")}`
8211
+ );
8212
+ return {
8213
+ success: true,
8214
+ results: {
8215
+ runtimeParamApplied: true,
8216
+ changes,
8217
+ runtime: { ...this.context.tasksRuntime ?? {} }
8218
+ }
8219
+ };
8220
+ }
8221
+ };
8222
+
7904
8223
  // src/tasks/TasksRegistry.js
7905
8224
  var TasksRegistry = class _TasksRegistry {
7906
8225
  /**
@@ -7919,7 +8238,7 @@ var TasksRegistry = class _TasksRegistry {
7919
8238
  * @returns {TasksRegistry}
7920
8239
  */
7921
8240
  static withCoreTasks() {
7922
- 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);
8241
+ 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);
7923
8242
  }
7924
8243
  /**
7925
8244
  * Register a single task class under a name. Overwrites any previous entry.
@@ -8025,7 +8344,9 @@ var SERVICE_TASK_NAMES = [
8025
8344
  "shellCommand",
8026
8345
  "systemInfo",
8027
8346
  "info",
8028
- "getLogs"
8347
+ "getLogs",
8348
+ "setRuntimeParam",
8349
+ "setRunnerParam"
8029
8350
  ];
8030
8351
  function normalizeAllowedTasks(value) {
8031
8352
  if (!value) return void 0;
@@ -8452,18 +8773,23 @@ async function claimNextRunnableTask(context, tasksTable, serviceGroup, registry
8452
8773
  async function runTasksLoop(context, options) {
8453
8774
  const queueName = options.queueName ?? "tasks";
8454
8775
  const target = options.target;
8455
- const pollMs = options.pollMs ?? 1e3;
8456
- const claimJitterMs = options.claimJitterMs ?? 0;
8457
- const maxParallel = options.maxParallel ?? 32;
8458
- const scanLimit = options.scanLimit ?? 100;
8459
8776
  const allowedTasks = normalizeAllowedTasks(options.allowedTasks);
8460
8777
  const registry = normalizeRegistry(options.registry);
8461
8778
  const { tasksTable, historyTable } = queueToTableNames(queueName);
8462
8779
  if (!target) throw new Error("runTasksLoop: target is required");
8463
8780
  context.tasksQueueName = queueName;
8781
+ ensureTasksRuntime(context, {
8782
+ maxParallel: options.maxParallel ?? 32,
8783
+ pollMs: options.pollMs ?? 1e3,
8784
+ claimJitterMs: options.claimJitterMs ?? 0,
8785
+ scanLimit: options.scanLimit ?? 100
8786
+ });
8787
+ if (typeof options.onRuntimeParam === "function") {
8788
+ context.tasksRuntimeOnParam = options.onRuntimeParam;
8789
+ }
8464
8790
  const runningPromises = /* @__PURE__ */ new Set();
8465
8791
  const runningTaskInstances = /* @__PURE__ */ new Map();
8466
- let runningStopControlPromise = null;
8792
+ let runningControlPromise = null;
8467
8793
  let stopRequested = false;
8468
8794
  let stopAllowanceMs = 5e3;
8469
8795
  context.tasksRunnerStop = false;
@@ -8474,9 +8800,16 @@ async function runTasksLoop(context, options) {
8474
8800
  if (hbGroup) {
8475
8801
  const hbIntervalMs = options.runnerHeartbeatIntervalMs ?? 1e4;
8476
8802
  const staleMs = options.runnerHeartbeatStaleMs ?? 45e3;
8803
+ const loop0 = readLoopRuntime(context);
8477
8804
  const defaultMeta = {
8478
8805
  component: "tasks-runner",
8479
- allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all"
8806
+ allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
8807
+ runtime: {
8808
+ maxParallel: loop0.maxParallel,
8809
+ pollMs: loop0.pollMs,
8810
+ claimJitterMs: loop0.claimJitterMs,
8811
+ scanLimit: loop0.scanLimit
8812
+ }
8480
8813
  };
8481
8814
  registryReg = await registerInServicesRegistry(context, {
8482
8815
  queueName,
@@ -8489,6 +8822,7 @@ async function runTasksLoop(context, options) {
8489
8822
  enforceMaxInstances: options.runnerEnforceMaxInstances ?? true,
8490
8823
  metadata: options.runnerMetadata ?? defaultMeta
8491
8824
  });
8825
+ context.servicesRegistry = registryReg;
8492
8826
  runnerIdentity = {
8493
8827
  service_name: registryReg.serviceName,
8494
8828
  server_name: import_node_os6.default.hostname(),
@@ -8502,22 +8836,23 @@ async function runTasksLoop(context, options) {
8502
8836
  }
8503
8837
  try {
8504
8838
  while (!context.isStop() && !stopRequested && context.tasksRunnerStop !== true) {
8505
- if (!runningStopControlPromise) {
8506
- const claimedStopTask = await claimNextRunnableTask(
8839
+ const { maxParallel, pollMs, claimJitterMs, scanLimit } = readLoopRuntime(context);
8840
+ if (!runningControlPromise) {
8841
+ const claimedControlTask = await claimNextRunnableTask(
8507
8842
  context,
8508
8843
  tasksTable,
8509
8844
  target,
8510
8845
  registry,
8511
8846
  10,
8512
- ["stopRunner", "stop"],
8847
+ controlLaneTaskNames(),
8513
8848
  runnerIdentity
8514
8849
  );
8515
- if (claimedStopTask) {
8516
- runningStopControlPromise = executeClaimedTask(
8850
+ if (claimedControlTask) {
8851
+ runningControlPromise = executeClaimedTask(
8517
8852
  context,
8518
8853
  tasksTable,
8519
8854
  historyTable,
8520
- claimedStopTask,
8855
+ claimedControlTask,
8521
8856
  registry,
8522
8857
  runningTaskInstances
8523
8858
  ).then(async (outcome) => {
@@ -8528,7 +8863,7 @@ async function runTasksLoop(context, options) {
8528
8863
  await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
8529
8864
  }
8530
8865
  }).finally(() => {
8531
- runningStopControlPromise = null;
8866
+ runningControlPromise = null;
8532
8867
  });
8533
8868
  }
8534
8869
  }
@@ -8559,8 +8894,8 @@ async function runTasksLoop(context, options) {
8559
8894
  runningPromises.add(p);
8560
8895
  }
8561
8896
  const wakePromises = [...runningPromises];
8562
- if (runningStopControlPromise) {
8563
- wakePromises.push(runningStopControlPromise);
8897
+ if (runningControlPromise) {
8898
+ wakePromises.push(runningControlPromise);
8564
8899
  }
8565
8900
  if (wakePromises.length === 0) {
8566
8901
  await sleepMs(pollMs);
@@ -8789,6 +9124,8 @@ var TasksManager = class _TasksManager {
8789
9124
  FooterPresets,
8790
9125
  GridCell,
8791
9126
  InputField,
9127
+ LOGGER_RUNTIME_KEYS,
9128
+ LOOP_RUNTIME_KEYS,
8792
9129
  ListComponent,
8793
9130
  ListItem,
8794
9131
  MultiColumnListComponent,
@@ -8807,6 +9144,7 @@ var TasksManager = class _TasksManager {
8807
9144
  TaskGetLogs,
8808
9145
  TaskPing,
8809
9146
  TaskSampleProcess,
9147
+ TaskSetRuntimeParam,
8810
9148
  TaskShellCommand,
8811
9149
  TaskStopRunner,
8812
9150
  TaskSumAB,
@@ -8818,12 +9156,16 @@ var TasksManager = class _TasksManager {
8818
9156
  activateRelease,
8819
9157
  appendDeployLog,
8820
9158
  appendTaskIpcLog,
9159
+ applyRuntimeParam,
9160
+ applyRuntimePatch,
8821
9161
  bootstrapHost,
8822
9162
  buildBreadcrumb,
8823
9163
  buildDetailBreadcrumb,
8824
9164
  buildFooter,
8825
9165
  bumpPatchVersion,
8826
9166
  cloneRepo,
9167
+ coerceRuntimeValue,
9168
+ controlLaneTaskNames,
8827
9169
  convertPattern,
8828
9170
  createRelease,
8829
9171
  defaultFileSynopsisFunction,
@@ -8845,11 +9187,13 @@ var TasksManager = class _TasksManager {
8845
9187
  ensureSchemaEverywhere,
8846
9188
  ensureTable,
8847
9189
  ensureTaskTables,
9190
+ ensureTasksRuntime,
8848
9191
  flushTaskIpcLogs,
8849
9192
  getArgsInstance,
8850
9193
  h,
8851
9194
  initServiceStructure,
8852
9195
  installDeps,
9196
+ installOperatorShell,
8853
9197
  ipcFileLogsTableNameForSourceResource,
8854
9198
  joiEdateType,
8855
9199
  joiStringArrayType,
@@ -8875,6 +9219,7 @@ var TasksManager = class _TasksManager {
8875
9219
  pullRepo,
8876
9220
  queueToTableNames,
8877
9221
  readCurrentRelease,
9222
+ readLoopRuntime,
8878
9223
  readReleaseBuildInfo,
8879
9224
  readTaskIpcLogsSnapshot,
8880
9225
  registerInServicesRegistry,