feat(FN-1808): merge fusion/fn-1808

This commit is contained in:
gsxdsm
2026-04-16 04:53:09 -07:00
parent 06383521b3
commit 72842d8687
10 changed files with 128 additions and 31 deletions

View File

@@ -503,27 +503,13 @@ export class Scheduler {
const inProgress = tasks.filter((t) => t.column === "in-progress");
// Specifying tasks (triage column, status "specifying") run full PI
// agent sessions that consume the same resources as execution agents,
// so they must occupy concurrency slots alongside in-progress tasks.
// Paused specifying tasks don't count toward slots.
const specifying = tasks.filter(
(t) => t.column === "triage" && t.status === "specifying" && !t.paused,
);
// When a semaphore is provided, it is the single source of truth for
// global concurrency — its availableCount already accounts for ALL
// slot holders (executors, specifiers, mergers). Counting specifying
// tasks in agentSlots as well would double-count them. Without a
// semaphore (fallback mode), count specifying tasks directly.
const agentSlots = this.options.semaphore
? inProgress.length
: inProgress.length + specifying.length;
// Execution tasks occupy concurrency slots governed by maxConcurrent.
// Triage/specification tasks have their own limit (maxTriageConcurrent)
// and do not count against this slot.
const agentSlots = inProgress.length;
// When a semaphore is provided, factor in its available slots so we
// don't schedule more tasks than the global limit allows. Triage and
// merge agents also hold semaphore slots, so availableCount may be
// lower than what maxConcurrent - inProgress.length would suggest.
// don't schedule more tasks than the global limit allows.
const semaphoreAvailable = this.options.semaphore
? this.options.semaphore.availableCount
: Infinity;

View File

@@ -545,16 +545,15 @@ export class TriageProcessor {
&& !(t.nextRecoveryAt && new Date(t.nextRecoveryAt).getTime() > now),
);
// Respect both per-project maxConcurrent and the global semaphore.
// Count all active agent slots: in-progress tasks + already-specifying tasks.
const maxConcurrent = settings.maxConcurrent ?? 2;
const inProgress = allTasks.filter((t) => t.column === "in-progress").length;
// Respect both per-project maxTriageConcurrent and the global semaphore.
// Only specifying tasks count against the triage limit; execution is governed by maxConcurrent.
const maxTriageConcurrent = settings.maxTriageConcurrent ?? settings.maxConcurrent ?? 2;
const specifying = allTasks.filter(
(t) => t.column === "triage" && t.status === "specifying" && !t.paused,
).length;
const activeAgents = inProgress + specifying;
const activeAgents = specifying;
const perProjectAvailable = Math.max(0, maxConcurrent - activeAgents);
const perProjectAvailable = Math.max(0, maxTriageConcurrent - activeAgents);
const semaphoreAvailable = this.options.semaphore
? Math.max(0, this.options.semaphore.availableCount)
: Infinity;
@@ -562,7 +561,7 @@ export class TriageProcessor {
if (maxToStart <= 0 && triageTasks.length > 0) {
triageLog.log(
`Triage throttled: ${activeAgents} active agents (${inProgress} executing, ${specifying} specifying), limit ${maxConcurrent}`,
`Triage throttled: ${activeAgents} specifying agents, limit ${maxTriageConcurrent}`,
);
}