diff options
| author | Adam Malczewski <[email protected]> | 2026-06-28 15:47:31 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-28 15:47:31 +0900 |
| commit | 213fe2ab2074b4536ae326d0007dc5e45507407c (patch) | |
| tree | 7423f7e4aab0fdf20f8416d385b563478f234132 | |
| parent | b60586285863f8bb82242a9df49c4d47e1235755 (diff) | |
| download | dispatch-213fe2ab2074b4536ae326d0007dc5e45507407c.tar.gz dispatch-213fe2ab2074b4536ae326d0007dc5e45507407c.zip | |
fix(predev): resolve merge conflicts between concurrency-fixes and workspace-star
| -rw-r--r-- | packages/provider-concurrency/src/concurrency-manager.test.ts | 62 | ||||
| -rw-r--r-- | packages/provider-concurrency/src/concurrency-manager.ts | 34 | ||||
| -rw-r--r-- | packages/provider-concurrency/tsconfig.json | 2 |
3 files changed, 49 insertions, 49 deletions
diff --git a/packages/provider-concurrency/src/concurrency-manager.test.ts b/packages/provider-concurrency/src/concurrency-manager.test.ts index 357a5d1..36c0ea3 100644 --- a/packages/provider-concurrency/src/concurrency-manager.test.ts +++ b/packages/provider-concurrency/src/concurrency-manager.test.ts @@ -504,7 +504,7 @@ describe("createConcurrencyManager", () => { const { manager, timers } = createManager({ releaseCooldownMs: 200 }); manager.setLimit("umans", 1); - const release1 = await manager.acquire("umans", "conv1", 0); + const release1 = await manager.acquire("umans", "conv1", "default", 0); expect(manager.getStatus("umans")?.cooldownMs).toBe(200); // Bump the cooldown to 500ms. @@ -513,7 +513,7 @@ describe("createConcurrencyManager", () => { // Queue a waiter. let resolved = false; - const promise2 = manager.acquire("umans", "conv2", 100).then((r) => { + const promise2 = manager.acquire("umans", "conv2", "default", 100).then((r) => { resolved = true; return r; }); @@ -549,7 +549,7 @@ describe("createConcurrencyManager", () => { manager.setCooldown("umans", 500); expect(manager.getCooldown("umans")).toBe(500); // No limit → acquire must be unlimited (no state with a limit imposed). - const release = await manager.acquire("umans", "conv1", 0); + const release = await manager.acquire("umans", "conv1", "default", 0); expect(typeof release).toBe("function"); release(); expect(manager.getStatus("umans")).toBeUndefined(); // no limit state created @@ -634,16 +634,16 @@ describe("createConcurrencyManager", () => { // Fill all 4 slots (fast-path, no gate). const releases = await Promise.all([ - manager.acquire("umans", "c1", 0), - manager.acquire("umans", "c2", 0), - manager.acquire("umans", "c3", 0), - manager.acquire("umans", "c4", 0), + manager.acquire("umans", "c1", "default", 0), + manager.acquire("umans", "c2", "default", 0), + manager.acquire("umans", "c3", "default", 0), + manager.acquire("umans", "c4", "default", 0), ]); expect(manager.getStatus("umans")?.inFlight).toBe(4); // 5th agent queues. let resolved = false; - const promise5 = manager.acquire("umans", "c5", 10).then((r) => { + const promise5 = manager.acquire("umans", "c5", "default", 10).then((r) => { resolved = true; return r; }); @@ -680,14 +680,14 @@ describe("createConcurrencyManager", () => { manager.setCooldown("umans", 0); const releases = await Promise.all([ - manager.acquire("umans", "c1", 0), - manager.acquire("umans", "c2", 0), - manager.acquire("umans", "c3", 0), - manager.acquire("umans", "c4", 0), + manager.acquire("umans", "c1", "default", 0), + manager.acquire("umans", "c2", "default", 0), + manager.acquire("umans", "c3", "default", 0), + manager.acquire("umans", "c4", "default", 0), ]); let resolved = false; - const promise5 = manager.acquire("umans", "c5", 10).then((r) => { + const promise5 = manager.acquire("umans", "c5", "default", 10).then((r) => { resolved = true; return r; }); @@ -714,9 +714,9 @@ describe("createConcurrencyManager", () => { manager.setLimit("umans", 1); manager.setCooldown("umans", 0); - const release1 = await manager.acquire("umans", "c1", 0); + const release1 = await manager.acquire("umans", "c1", "default", 0); let resolved = false; - const promise2 = manager.acquire("umans", "c2", 10).then((r) => { + const promise2 = manager.acquire("umans", "c2", "default", 10).then((r) => { resolved = true; return r; }); @@ -740,20 +740,20 @@ describe("createConcurrencyManager", () => { manager.setCooldown("umans", 0); const releases = await Promise.all([ - manager.acquire("umans", "c1", 0), - manager.acquire("umans", "c2", 0), - manager.acquire("umans", "c3", 0), - manager.acquire("umans", "c4", 0), + manager.acquire("umans", "c1", "default", 0), + manager.acquire("umans", "c2", "default", 0), + manager.acquire("umans", "c3", "default", 0), + manager.acquire("umans", "c4", "default", 0), ]); // Queue two waiters. let r5 = false; let r6 = false; - const p5 = manager.acquire("umans", "c5", 10).then((r) => { + const p5 = manager.acquire("umans", "c5", "default", 10).then((r) => { r5 = true; return r; }); - const p6 = manager.acquire("umans", "c6", 20).then((r) => { + const p6 = manager.acquire("umans", "c6", "default", 20).then((r) => { r6 = true; return r; }); @@ -784,9 +784,9 @@ describe("createConcurrencyManager", () => { manager.setLimit("umans", 1); manager.setCooldown("umans", 0); - return manager.acquire("umans", "c1", 0).then(async (release1) => { + return manager.acquire("umans", "c1", "default", 0).then(async (release1) => { // Queue a waiter (arms the 1s fallback timer). - const p2 = manager.acquire("umans", "c2", 10); + const p2 = manager.acquire("umans", "c2", "default", 10); await Promise.resolve(); await Promise.resolve(); @@ -818,12 +818,12 @@ describe("createConcurrencyManager", () => { manager.setCooldown("umans", 0); // Hold the single slot. - const release1 = await manager.acquire("umans", "c1", 0); + const release1 = await manager.acquire("umans", "c1", "default", 0); expect(manager.getStatus("umans")?.inFlight).toBe(1); // Queue a waiter (c2). Cooldown is 0, but the gate defers admission until a poll. let c2Granted = false; - const p2 = manager.acquire("umans", "c2", 10).then((r) => { + const p2 = manager.acquire("umans", "c2", "default", 10).then((r) => { c2Granted = true; return r; }); @@ -842,7 +842,7 @@ describe("createConcurrencyManager", () => { // not fast-path. Even if it saw inFlight < limit, gatePolling would route it // through the queue. Either way it must NOT be granted yet. let c3Granted = false; - const p3 = manager.acquire("umans", "c3", 20).then((r) => { + const p3 = manager.acquire("umans", "c3", "default", 20).then((r) => { c3Granted = true; return r; }); @@ -870,7 +870,7 @@ describe("createConcurrencyManager", () => { manager.setLimit("umans", 4); // Nowhere near the limit, no recycle in progress → fast-path, no poll. - const release = await manager.acquire("umans", "c1", 0); + const release = await manager.acquire("umans", "c1", "default", 0); expect(manager.getStatus("umans")?.inFlight).toBe(1); release(); }); @@ -890,9 +890,9 @@ describe("createConcurrencyManager", () => { manager.setLimit("umans", 1); manager.setCooldown("umans", 0); - const release1 = await manager.acquire("umans", "c1", 0); + const release1 = await manager.acquire("umans", "c1", "default", 0); // Queue a waiter; release → recycle → poll THROWS. - const p2 = manager.acquire("umans", "c2", 10); + const p2 = manager.acquire("umans", "c2", "default", 10); await Promise.resolve(); await Promise.resolve(); @@ -918,8 +918,8 @@ describe("createConcurrencyManager", () => { manager.setLimit("umans", 1); manager.setCooldown("umans", 0); - const release1 = await manager.acquire("umans", "c1", 0); - manager.acquire("umans", "c2", 10).then((r) => r()); // queue + auto-release + const release1 = await manager.acquire("umans", "c1", "default", 0); + manager.acquire("umans", "c2", "default", 10).then((r) => r()); // queue + auto-release await Promise.resolve(); await Promise.resolve(); release1(); diff --git a/packages/provider-concurrency/src/concurrency-manager.ts b/packages/provider-concurrency/src/concurrency-manager.ts index ea66a49..986493d 100644 --- a/packages/provider-concurrency/src/concurrency-manager.ts +++ b/packages/provider-concurrency/src/concurrency-manager.ts @@ -376,12 +376,6 @@ export function createConcurrencyManager(opts: ConcurrencyManagerOpts): Concurre } /** - * Grant queued waiters WITHOUT the usage gate (the fast path used when no - * `fetchUsage` is configured, or as the cooldown-only fallback when a poll - * returns no usage info). Grants while there is internal room - * (`inFlight < limit`). Synchronous. - */ - function grantLoop(state: ProviderState, providerId: string): void { * Priority comparator for queued waiters: starred-workspace agents first, * then oldest-agent-first (ascending `promptStartedAt`) within each group. * Called at sort time (both on insert and before granting) so a workspace @@ -394,10 +388,15 @@ export function createConcurrencyManager(opts: ConcurrencyManagerOpts): Concurre return a.promptStartedAt - b.promptStartedAt; // oldest first within group } - function tryGrantNext(providerId: string): void { - const state = states.get(providerId); - if (state === undefined) return; - if (state.paused) return; + /** + * Grant queued waiters WITHOUT the usage gate (the fast path used when no + * `fetchUsage` is configured, or as the cooldown-only fallback when a poll + * returns no usage info). Grants while there is internal room + * (`inFlight < limit`). Synchronous. Re-sorts with {@link compareWaiters} + * (starred-workspace-first, then oldest-agent-first) before granting so a + * workspace starred AFTER an agent queued is re-evaluated. + */ + function grantLoop(state: ProviderState, providerId: string): void { // Re-sort before granting: a workspace may have been starred/unstarred // since the waiters were enqueued, so priority may have changed. state.queue.sort(compareWaiters); @@ -425,6 +424,10 @@ export function createConcurrencyManager(opts: ConcurrencyManagerOpts): Concurre function grantOne(state: ProviderState, providerId: string): void { if (state.queue.length === 0) return; if (state.inFlight >= state.limit) return; + // Re-sort before picking the front: starred-workspace agents must be + // admitted first, even on the usage-gated path (a workspace may have been + // starred since the waiters were enqueued). + state.queue.sort(compareWaiters); const waiter = state.queue[0]; if (waiter === undefined) return; state.queue.shift(); @@ -621,19 +624,16 @@ export function createConcurrencyManager(opts: ConcurrencyManagerOpts): Concurre // Queue (starred-workspace-first, then oldest-agent-first). return new Promise<() => void>((resolve) => { - state.queue.push({ conversationId, promptStartedAt, resolve }); - // Keep sorted ascending by promptStartedAt (oldest first). - state.queue.sort((a, b) => a.promptStartedAt - b.promptStartedAt); + state.queue.push({ conversationId, workspaceId, promptStartedAt, resolve }); + // Keep sorted by priority (starred first, then oldest-agent-first). + // The queue is typically tiny (<20), so a simple sort is fine. + state.queue.sort(compareWaiters); // If the usage gate is active, ensure the fallback repoll timer is // armed (a release may not come for a while; the 1s timer covers an // upstream count that drops on its own). if (fetchUsage !== undefined) { armGateRepoll(providerId, state); } - state.queue.push({ conversationId, workspaceId, promptStartedAt, resolve }); - // Keep sorted by priority (starred first, then oldest-agent-first). - // The queue is typically tiny (<20), so a simple sort is fine. - state.queue.sort(compareWaiters); }); }, diff --git a/packages/provider-concurrency/tsconfig.json b/packages/provider-concurrency/tsconfig.json index 44ed916..a4b26ef 100644 --- a/packages/provider-concurrency/tsconfig.json +++ b/packages/provider-concurrency/tsconfig.json @@ -2,5 +2,5 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "src", "outDir": "dist", "composite": true }, "include": ["src/**/*.ts"], - "references": [{ "path": "../kernel" }] + "references": [{ "path": "../conversation-store" }, { "path": "../kernel" }] } |
