feat(HAI-016): complete Step 4 — integrate semaphore into merge path

This commit is contained in:
Dustin Byrne
2026-03-25 21:21:42 -04:00
parent d487ebee51
commit 6a45531a79
2 changed files with 69 additions and 3 deletions

View File

@@ -1,7 +1,7 @@
import { exec } from "node:child_process";
import { TaskStore } from "@hai/core";
import { createServer } from "@hai/dashboard";
import { TriageProcessor, TaskExecutor, Scheduler, aiMergeTask } from "@hai/engine";
import { TriageProcessor, TaskExecutor, Scheduler, AgentSemaphore, aiMergeTask } from "@hai/engine";
function openBrowser(url: string): void {
const cmd =
@@ -17,13 +17,32 @@ export async function runDashboard(port: number, opts: { engine?: boolean; open?
await store.init();
await store.watch();
// AI-powered merge handler (used by the web UI for manual merges)
const onMerge = (taskId: string) =>
// ── Shared concurrency semaphore ──────────────────────────────────
//
// Gates all agentic activities (triage, execution, merge) behind a
// single slot limit so they collectively respect settings.maxConcurrent.
// Created eagerly so the merge queue can reference it; the engine block
// below passes it to triage/executor/scheduler as well.
//
// The limit is read from a cached value that is refreshed from the store
// on each scheduler poll cycle (see engine block below). This avoids
// async I/O in the synchronous getter while still picking up live changes.
//
const initialSettings = await store.getSettings();
let cachedMaxConcurrent = initialSettings.maxConcurrent;
const semaphore = new AgentSemaphore(() => cachedMaxConcurrent);
// AI-powered merge handler (used by the web UI for manual merges).
// Wrapped with the shared semaphore so merges count toward the global
// concurrency limit alongside triage and execution agents.
const rawMerge = (taskId: string) =>
aiMergeTask(store, cwd, taskId, {
onAgentText: (delta) => process.stdout.write(delta),
onAgentTool: (name) => console.log(`[merger] tool: ${name}`),
});
const onMerge = (taskId: string) => semaphore.run(() => rawMerge(taskId));
// ── Serialized auto-merge queue ─────────────────────────────────────
//
// Three paths feed into this queue: