@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/package.json +1 -1
- package/plugins/sp/README.md +17 -11
- package/plugins/sp/lib/idea-handoff.generated.mjs +53 -25
- package/plugins/sp/plugin.json +1 -1
- package/schemas/spur-config.schema.json +5 -0
- package/spur.js +131 -34
- package/plugins/README.md +0 -656
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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, [
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
121819
|
-
|
|
121820
|
-
|
|
121821
|
-
|
|
121822
|
-
|
|
121823
|
-
|
|
121824
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121845
|
-
name: job.name
|
|
121846
|
-
|
|
121847
|
-
|
|
121848
|
-
|
|
121849
|
-
}
|
|
121850
|
-
}
|
|
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);
|