@gobing-ai/spur 0.3.82 → 0.3.83

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.3.82",
3
+ "version": "0.3.83",
4
4
  "description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
5
5
  "extensions": {
6
6
  "pi": ["./hooks/pi/guard-extension.ts"]
@@ -312,6 +312,11 @@
312
312
  "type": "object",
313
313
  "description": "Release knobs for bump-ver/drop-tags. Defaults: -v separator, publish.yml workflow, chore(release) commit, gh run list limit 5.",
314
314
  "properties": {
315
+ "aggregatePackage": {
316
+ "type": "string",
317
+ "minLength": 1,
318
+ "description": "Package whose own release tag is the publish trigger for the aggregate (--all) path, as an unscoped package id (e.g. \"spur\" for @gobing-ai/spur). Default: the package whose full name equals the workspace root manifest name."
319
+ },
315
320
  "tagVersionSeparator": {
316
321
  "type": "string",
317
322
  "minLength": 1,
package/spur.js CHANGED
@@ -39789,6 +39789,7 @@ var init_src = __esm(() => {
39789
39789
  VersionCarrierEntrySchema = exports_external.looseObject({ type: exports_external.string().min(1) });
39790
39790
  BuilderBumpVerConfigSchema = exports_external.object({
39791
39791
  tagVersionSeparator: exports_external.string().min(1).default("-v"),
39792
+ aggregatePackage: exports_external.string().min(1).optional(),
39792
39793
  publishWorkflow: exports_external.string().min(1).default("publish.yml"),
39793
39794
  releaseCommitType: exports_external.string().min(1).default("chore"),
39794
39795
  releaseCommitScope: exports_external.string().min(1).default("release"),
@@ -68068,6 +68069,7 @@ async function applyCliMigrations(adapter, migrations = CLI_MIGRATIONS) {
68068
68069
  const runsStatusDoneSkip = migration.id === "0017_spur_cli_runs_status_completed_to_done" && (!await tableExists(adapter, "runs") || !await columnExists(adapter, "runs", "status"));
68069
68070
  const historyBoardToolStatsColumnsSkip = migration.id === "0031_spur_cli_history_board_tool_stats_columns" && (!await tableExists(adapter, "history_board_tool_stats") || await columnExists(adapter, "history_board_tool_stats", "fresh_input_tokens"));
68070
68071
  const queueJobsActiveIndexSkip = migration.id === "0027_spur_cli_history_refresh_active_unique" && !await tableExists(adapter, "queue_jobs");
68072
+ const schedulerCustomActiveIndexSkip = migration.id === "0047_spur_cli_scheduler_custom_active_unique" && !await tableExists(adapter, "queue_jobs");
68071
68073
  const queueJobsDeadlineLeaseSkip = migration.id === "0041_spur_cli_queue_jobs_deadline_lease_columns" && !await tableExists(adapter, "queue_jobs");
68072
68074
  const inboxRequestKeySkip = migration.id === "0043_spur_cli_inbox_messages_request_key" && !await tableExists(adapter, "inbox_messages");
68073
68075
  const coordinationReceiptColumnsSkip = migration.id === "0044_spur_cli_coordination_runs_receipt_columns" && !await tableExists(adapter, "coordination_runs");
@@ -68076,7 +68078,7 @@ async function applyCliMigrations(adapter, migrations = CLI_MIGRATIONS) {
68076
68078
  const historyRollupWatermarkSkip = migration.id === "0036_spur_cli_history_rollup_watermark" && (await tableExists(adapter, "history_board_rollup_watermark") || !await tableExists(adapter, "history_message") || !await columnExists(adapter, "history_message", "imported_at"));
68077
68079
  const boundedRollupDerivationsSkip = migration.id === "0039_spur_cli_bounded_rollup_derivations" && (!await tableExists(adapter, "history_message") || !await columnExists(adapter, "history_message", "source_file") || !await tableExists(adapter, "history_board_source_daily"));
68078
68080
  const historyModelFallbackIndexSkip = migration.id === "0042_spur_cli_history_model_fallback_index" && (!await tableExists(adapter, "history_message") || !await columnExists(adapter, "history_message", "model"));
68079
- if (shouldApplySql && !sequenceIndexSkip && !argsRawSkip && !runsStatusDoneSkip && !nameOccurredIndexSkip && !historyBoardQueryIndexesSkip && !historyPerformanceIndexesSkip && !historyToolCallIndexesSkip && !historyBoardCoveringIndexesSkip && !historyBoardToolStatsColumnsSkip && !callIdSkip && !tsNullableSkip && !queueJobsActiveIndexSkip && !queueJobsDeadlineLeaseSkip && !inboxRequestKeySkip && !coordinationReceiptColumnsSkip && !historyToolIdentitySkip && !historyMeasureVectorSkip && !historyRollupWatermarkSkip && !boundedRollupDerivationsSkip && !historyModelFallbackIndexSkip) {
68081
+ if (shouldApplySql && !sequenceIndexSkip && !argsRawSkip && !runsStatusDoneSkip && !nameOccurredIndexSkip && !historyBoardQueryIndexesSkip && !historyPerformanceIndexesSkip && !historyToolCallIndexesSkip && !historyBoardCoveringIndexesSkip && !historyBoardToolStatsColumnsSkip && !callIdSkip && !tsNullableSkip && !queueJobsActiveIndexSkip && !schedulerCustomActiveIndexSkip && !queueJobsDeadlineLeaseSkip && !inboxRequestKeySkip && !coordinationReceiptColumnsSkip && !historyToolIdentitySkip && !historyMeasureVectorSkip && !historyRollupWatermarkSkip && !boundedRollupDerivationsSkip && !historyModelFallbackIndexSkip) {
68080
68082
  for (const statement of splitSqlStatements(migration.sql)) {
68081
68083
  await adapter.exec(statement);
68082
68084
  }
@@ -68189,6 +68191,15 @@ CREATE INDEX IF NOT EXISTS queue_jobs_ready_idx ON queue_jobs (status, next_retr
68189
68191
  -- jobs that legitimately have multiple active rows concurrently, so a global
68190
68192
  -- (type, status) unique index would break them.
68191
68193
  CREATE UNIQUE INDEX IF NOT EXISTS queue_jobs_history_refresh_active_unique ON queue_jobs (type) WHERE type = 'history.refresh' AND status IN ('pending', 'processing');
68194
+
68195
+ -- At most one ACTIVE configured-scheduler job per job NAME (task 0863): the tick
68196
+ -- reads findActiveSchedulerCustomJob and then inserts, which is not atomic across
68197
+ -- spur serve daemons sharing one project DB \u2014 every daemon's cron occurrence used
68198
+ -- to insert its own row (3-14 per tick on 2026-09-14/15). The name lives inside the
68199
+ -- JSON payload, so this is an expression index \u2014 scoped to scheduler.custom +
68200
+ -- active rows exactly like the 0716 history.refresh index, so terminal rows and
68201
+ -- other job types (task-action, feature-action, history.refresh) stay unaffected.
68202
+ CREATE UNIQUE INDEX IF NOT EXISTS queue_jobs_scheduler_custom_active_unique ON queue_jobs (json_extract(payload, '$.name')) WHERE type = 'scheduler.custom' AND status IN ('pending', 'processing');
68192
68203
  `, SYSTEM_EVENTS_SCHEMA_SQL = `
68193
68204
  CREATE TABLE IF NOT EXISTS system_events (
68194
68205
  id TEXT PRIMARY KEY,
@@ -68630,6 +68641,25 @@ CREATE TABLE IF NOT EXISTS agent_instances (
68630
68641
  CREATE INDEX IF NOT EXISTS idx_agent_instances_role ON agent_instances (role);
68631
68642
  CREATE INDEX IF NOT EXISTS idx_agent_instances_executor ON agent_instances (executor);
68632
68643
  CREATE INDEX IF NOT EXISTS idx_agent_instances_team ON agent_instances (team_id);
68644
+ `, SCHEDULER_CUSTOM_ACTIVE_UNIQUE_SCHEMA_SQL = `
68645
+ UPDATE queue_jobs
68646
+ SET status = 'failed',
68647
+ last_error = 'retired by migration 0047_spur_cli_scheduler_custom_active_unique: superseded duplicate active scheduler.custom job name',
68648
+ processing_at = NULL,
68649
+ updated_at = CAST(strftime('%s', 'now') AS INTEGER) * 1000
68650
+ WHERE type = 'scheduler.custom'
68651
+ AND status IN ('pending', 'processing')
68652
+ AND id NOT IN (
68653
+ SELECT id FROM (
68654
+ SELECT id, ROW_NUMBER() OVER (
68655
+ PARTITION BY json_extract(payload, '$.name') ORDER BY created_at ASC, id ASC
68656
+ ) AS rank
68657
+ FROM queue_jobs
68658
+ WHERE type = 'scheduler.custom' AND status IN ('pending', 'processing')
68659
+ ) WHERE rank = 1
68660
+ );
68661
+
68662
+ CREATE UNIQUE INDEX IF NOT EXISTS queue_jobs_scheduler_custom_active_unique ON queue_jobs (json_extract(payload, '$.name')) WHERE type = 'scheduler.custom' AND status IN ('pending', 'processing');
68633
68663
  `, HISTORY_REFRESH_ACTIVE_UNIQUE_SCHEMA_SQL = `
68634
68664
  UPDATE queue_jobs
68635
68665
  SET status = 'failed',
@@ -69031,6 +69061,10 @@ ${HISTORY_TASK_SESSION_SCHEMA_SQL}
69031
69061
  {
69032
69062
  id: "0046_spur_cli_project_strategy",
69033
69063
  sql: PROJECT_STRATEGY_SCHEMA_SQL
69064
+ },
69065
+ {
69066
+ id: "0047_spur_cli_scheduler_custom_active_unique",
69067
+ sql: SCHEDULER_CUSTOM_ACTIVE_UNIQUE_SCHEMA_SQL
69034
69068
  }
69035
69069
  ];
69036
69070
  });
@@ -86369,6 +86403,14 @@ var init_job_exclusion_guard = __esm(() => {
86369
86403
  });
86370
86404
 
86371
86405
  // ../../packages/app/src/services/scheduler-custom-job-service.ts
86406
+ function isSchedulerCustomActiveConflict(error51) {
86407
+ if (typeof error51 !== "object" || error51 === null)
86408
+ return false;
86409
+ const { code, message } = error51;
86410
+ if (typeof message !== "string" || !message.includes(SCHEDULER_CUSTOM_ACTIVE_INDEX))
86411
+ return false;
86412
+ return code === undefined || String(code).startsWith("SQLITE_CONSTRAINT");
86413
+ }
86372
86414
  function resolveSchedulerCustomTimeoutMs(env) {
86373
86415
  return normalizeLegacyTimeoutMs(env.SPUR_SCHEDULER_CUSTOM_TIMEOUT_MS, SCHEDULER_CUSTOM_TIMEOUT_MS);
86374
86416
  }
@@ -86408,7 +86450,8 @@ function validateSchedulerCustomJobPayload(raw) {
86408
86450
  async function handleSchedulerCustomJob(deps, job) {
86409
86451
  const payload = validateSchedulerCustomJobPayload(job.payload);
86410
86452
  if (activeJobs.has(payload.name)) {
86411
- throw new Error(`scheduler job "${payload.name}" is already running in this process; skipping duplicate execution`);
86453
+ deps.onDuplicate?.(payload.name);
86454
+ return;
86412
86455
  }
86413
86456
  const exclusiveKey = payload.exclusiveKey;
86414
86457
  if (exclusiveKey !== undefined) {
@@ -86444,7 +86487,7 @@ async function handleSchedulerCustomJob(deps, job) {
86444
86487
  releaseExclusiveJob(exclusiveKey, `scheduler job "${payload.name}"`);
86445
86488
  }
86446
86489
  }
86447
- var SCHEDULER_CUSTOM_JOB = "scheduler.custom", SCHEDULER_CUSTOM_TIMEOUT_MS = 600000, SCHEDULER_CUSTOM_MAX_OUTPUT = 1e6, activeJobs;
86490
+ var SCHEDULER_CUSTOM_JOB = "scheduler.custom", SCHEDULER_CUSTOM_ACTIVE_INDEX = "queue_jobs_scheduler_custom_active_unique", SCHEDULER_CUSTOM_TIMEOUT_MS = 600000, SCHEDULER_CUSTOM_MAX_OUTPUT = 1e6, activeJobs;
86448
86491
  var init_scheduler_custom_job_service = __esm(() => {
86449
86492
  init_execution_policy();
86450
86493
  init_job_exclusion_guard();
@@ -97940,6 +97983,7 @@ __export(exports_src2, {
97940
97983
  materializeRoster: () => materializeRoster,
97941
97984
  looksLikeOpaqueId: () => looksLikeOpaqueId,
97942
97985
  isSystemEventEnvelopeV2: () => isSystemEventEnvelopeV2,
97986
+ isSchedulerCustomActiveConflict: () => isSchedulerCustomActiveConflict,
97943
97987
  isRecordAuthoredReview: () => isRecordAuthoredReview,
97944
97988
  isPortLive: () => isPortLive,
97945
97989
  isPortAvailable: () => isPortAvailable,
@@ -98054,6 +98098,7 @@ __export(exports_src2, {
98054
98098
  STRATEGIES: () => STRATEGIES,
98055
98099
  SCHEDULER_CUSTOM_TIMEOUT_MS: () => SCHEDULER_CUSTOM_TIMEOUT_MS,
98056
98100
  SCHEDULER_CUSTOM_JOB: () => SCHEDULER_CUSTOM_JOB,
98101
+ SCHEDULER_CUSTOM_ACTIVE_INDEX: () => SCHEDULER_CUSTOM_ACTIVE_INDEX,
98057
98102
  RunStoreService: () => RunStoreService,
98058
98103
  RunStoreNotFoundError: () => RunStoreNotFoundError,
98059
98104
  RunStoreBadCursorError: () => RunStoreBadCursorError,
@@ -106397,6 +106442,11 @@ var spur_config_schema_default = {
106397
106442
  type: "object",
106398
106443
  description: "Release knobs for bump-ver/drop-tags. Defaults: -v separator, publish.yml workflow, chore(release) commit, gh run list limit 5.",
106399
106444
  properties: {
106445
+ aggregatePackage: {
106446
+ type: "string",
106447
+ minLength: 1,
106448
+ description: 'Package whose own release tag is the publish trigger for the aggregate (--all) path, as an unscoped package id (e.g. "spur" for @gobing-ai/spur). Default: the package whose full name equals the workspace root manifest name.'
106449
+ },
106400
106450
  tagVersionSeparator: {
106401
106451
  type: "string",
106402
106452
  minLength: 1,
@@ -108884,6 +108934,19 @@ var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
108884
108934
  function releaseTag(config4, version3) {
108885
108935
  return `${config4.packageName}${config4.tagVersionSeparator}${version3}`;
108886
108936
  }
108937
+ function resolveAggregatePackage(aggregatePackage, packages, rootName) {
108938
+ if (aggregatePackage !== undefined) {
108939
+ const config4 = packages.get(aggregatePackage);
108940
+ if (config4 === undefined) {
108941
+ throw new Error(`unknown builder.bump-ver.aggregatePackage "${aggregatePackage}". Package IDs: ${[...packages.keys()].join(", ")}`);
108942
+ }
108943
+ return config4;
108944
+ }
108945
+ return [...packages.values()].find((c3) => c3.packageName === rootName);
108946
+ }
108947
+ function resolveAggregateTag(ctx, version3) {
108948
+ return ctx.aggregate === undefined ? `${ctx.rootName}-v${version3}` : releaseTag(ctx.aggregate, version3);
108949
+ }
108887
108950
  async function run(repoRoot, cmd) {
108888
108951
  const executor = new NodeProcessExecutor3({ defaultTimeout: 30000, defaultMaxOutput: 512000 });
108889
108952
  const result = await executor.run({
@@ -109005,9 +109068,11 @@ async function releaseContext(repoRoot, bumpVerConfig = resolveBuilderBumpVerCon
109005
109068
  }
109006
109069
  }
109007
109070
  }
109008
- pinnedNames.add(rootName);
109071
+ const aggregate2 = resolveAggregatePackage(bumpVerConfig.aggregatePackage, packages, rootName);
109072
+ if (aggregate2 !== undefined)
109073
+ pinnedNames.add(aggregate2.packageName);
109009
109074
  const allPackages = [...packages.values()].filter((c3) => pinnedNames.has(c3.packageName));
109010
- return { repoRoot, rootName, packages, allPackages, versionCarriers };
109075
+ return { repoRoot, rootName, aggregate: aggregate2, packages, allPackages, versionCarriers };
109011
109076
  }
109012
109077
  async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output2) {
109013
109078
  const changed = [];
@@ -109188,7 +109253,7 @@ async function bumpAll(ctx, version3, options, output2) {
109188
109253
  }
109189
109254
  const branch = await assertCleanTreeOnBranch(ctx);
109190
109255
  const configs = ctx.allPackages;
109191
- const aggregateTag = `${ctx.rootName}-v${version3}`;
109256
+ const aggregateTag = resolveAggregateTag(ctx, version3);
109192
109257
  const existingLocal = new Set((await git2(ctx.repoRoot, ["tag", "-l"])).split(`
109193
109258
  `).filter(Boolean));
109194
109259
  const allTags = [aggregateTag, ...configs.map((c3) => releaseTag(c3, version3))];
@@ -109244,7 +109309,13 @@ async function bumpAll(ctx, version3, options, output2) {
109244
109309
  output2.write(`Tagged (trace): ${tag}`);
109245
109310
  }
109246
109311
  }
109247
- await git2(ctx.repoRoot, ["tag", "-a", aggregateTag, "-m", `${ctx.rootName} ${version3} \u2014 ${shortNames}`]);
109312
+ await git2(ctx.repoRoot, [
109313
+ "tag",
109314
+ "-a",
109315
+ aggregateTag,
109316
+ "-m",
109317
+ `${ctx.aggregate?.packageName ?? ctx.rootName} ${version3} \u2014 ${shortNames}`
109318
+ ]);
109248
109319
  output2.write(`Tagged (publish): ${aggregateTag}`);
109249
109320
  if (!options.push) {
109250
109321
  output2.write(`
@@ -109288,7 +109359,7 @@ async function dropAll(ctx, version3, options, output2) {
109288
109359
  for (const config4 of ctx.allPackages) {
109289
109360
  await dropTagsFor(ctx, config4, version3, options, output2);
109290
109361
  }
109291
- const aggregateTag = `${ctx.rootName}-v${version3}`;
109362
+ const aggregateTag = resolveAggregateTag(ctx, version3);
109292
109363
  const localTags = new Set((await git2(ctx.repoRoot, ["tag", "-l"])).split(`
109293
109364
  `).filter(Boolean));
109294
109365
  if (localTags.has(aggregateTag)) {
@@ -109829,7 +109900,7 @@ import { createRequire } from "module";
109829
109900
  var CLI_CONFIG = {
109830
109901
  binaryName: "spur",
109831
109902
  binaryLabel: "spur",
109832
- binaryVersion: "0.3.82",
109903
+ binaryVersion: "0.3.83",
109833
109904
  configDir: ".spur",
109834
109905
  configFile: ".spur/config.yaml",
109835
109906
  databaseFile: ".spur/spur.db"
@@ -121813,41 +121884,58 @@ function registerSchedulerEntries(scheduler3, ctx, jobs = [], options = {}) {
121813
121884
  const sweepThresholdMs = jobPolicy === null ? null : jobPolicy + resolveKillGraceMs3(env);
121814
121885
  register(schedule, `${SCHEDULER_CUSTOM_JOB}:${job.name}`, async () => {
121815
121886
  const queue = await ctx.jobQueue();
121887
+ const enqueueFresh = () => queue.enqueue(SCHEDULER_CUSTOM_JOB, {
121888
+ name: job.name,
121889
+ command: job.command,
121890
+ ...historyProducerExclusiveKeyFor(job.command) !== undefined && {
121891
+ exclusiveKey: historyProducerExclusiveKeyFor(job.command)
121892
+ }
121893
+ }, { maxRetries: 1 });
121894
+ try {
121895
+ await enqueueFresh();
121896
+ return;
121897
+ } catch (error51) {
121898
+ if (!isSchedulerCustomActiveConflict(error51))
121899
+ throw error51;
121900
+ }
121816
121901
  const db2 = await ctx.getDb();
121817
121902
  const active = await findActiveSchedulerCustomJob(db2, job.name);
121818
- if (active !== undefined) {
121819
- const now3 = Date.now();
121820
- const stale = sweepThresholdMs !== null && active.status === "processing" && now3 - (active.processingAt ?? active.updatedAt) > sweepThresholdMs;
121821
- if (stale) {
121822
- const reason = `age-sweep: processing exceeded ${sweepThresholdMs}ms`;
121823
- const swept = await failStaleSchedulerCustomJob(db2, active.id, now3, reason);
121824
- if (swept) {
121903
+ const now3 = Date.now();
121904
+ const stale = active !== undefined && sweepThresholdMs !== null && active.status === "processing" && now3 - (active.processingAt ?? active.updatedAt) > sweepThresholdMs;
121905
+ if (active !== undefined && stale && sweepThresholdMs !== null) {
121906
+ const reason = `age-sweep: processing exceeded ${sweepThresholdMs}ms`;
121907
+ const swept = await failStaleSchedulerCustomJob(db2, active.id, now3, reason);
121908
+ if (swept) {
121909
+ ctx.eventBus().emit("scheduler.job.executed", {
121910
+ name: `${SCHEDULER_CUSTOM_JOB}:${job.name}`,
121911
+ durationMs: 0,
121912
+ severity: "info",
121913
+ swept: true,
121914
+ reason
121915
+ });
121916
+ try {
121917
+ await enqueueFresh();
121918
+ } catch (error51) {
121919
+ if (!isSchedulerCustomActiveConflict(error51))
121920
+ throw error51;
121825
121921
  ctx.eventBus().emit("scheduler.job.executed", {
121826
121922
  name: `${SCHEDULER_CUSTOM_JOB}:${job.name}`,
121827
121923
  durationMs: 0,
121828
121924
  severity: "info",
121829
- swept: true,
121830
- reason
121925
+ skipped: true,
121926
+ reason: "post-sweep enqueue lost to a concurrent tick"
121831
121927
  });
121832
121928
  }
121833
- } else {
121834
- ctx.eventBus().emit("scheduler.job.executed", {
121835
- name: `${SCHEDULER_CUSTOM_JOB}:${job.name}`,
121836
- durationMs: 0,
121837
- severity: "info",
121838
- skipped: true,
121839
- reason: `active job ${active.id} already exists`
121840
- });
121841
121929
  return;
121842
121930
  }
121843
121931
  }
121844
- await queue.enqueue(SCHEDULER_CUSTOM_JOB, {
121845
- name: job.name,
121846
- command: job.command,
121847
- ...historyProducerExclusiveKeyFor(job.command) !== undefined && {
121848
- exclusiveKey: historyProducerExclusiveKeyFor(job.command)
121849
- }
121850
- }, { maxRetries: 1 });
121932
+ ctx.eventBus().emit("scheduler.job.executed", {
121933
+ name: `${SCHEDULER_CUSTOM_JOB}:${job.name}`,
121934
+ durationMs: 0,
121935
+ severity: "info",
121936
+ skipped: true,
121937
+ reason: `active job ${active?.id ?? "unknown"} already exists`
121938
+ });
121851
121939
  });
121852
121940
  registrations.push({
121853
121941
  name: job.name,
@@ -122126,7 +122214,16 @@ async function startServer(options, deps = defaultDeps) {
122126
122214
  return handleSchedulerCustomJob({
122127
122215
  cwd: ctx.cwd,
122128
122216
  executor: childExecutor,
122129
- resolveTimeoutMs: (name) => resolveSchedulerJobTimeoutMs(name, env, schedulerCustomTimeoutMs)
122217
+ resolveTimeoutMs: (name) => resolveSchedulerJobTimeoutMs(name, env, schedulerCustomTimeoutMs),
122218
+ onDuplicate: (name) => {
122219
+ ctx.eventBus().emit("scheduler.job.executed", {
122220
+ name: `${SCHEDULER_CUSTOM_JOB}:${name}`,
122221
+ durationMs: 0,
122222
+ severity: "info",
122223
+ skipped: true,
122224
+ reason: `duplicate execution suppressed; ${name} is already running in this process`
122225
+ });
122226
+ }
122130
122227
  }, job);
122131
122228
  });
122132
122229
  const unlimitedJobNames = appRt.config.scheduler.jobs.filter((job) => resolveSchedulerJobTimeoutMs(job.name, env, schedulerCustomTimeoutMs) === null).map((job) => job.name);