feat(FN-5257): restore visible action icons in SecretsView
Restores visibility of action icons in SecretsView (CSS styling and component adjustments), accompanied by updated test coverage. A test stabilization commit for the process supervisor fallback rounds out the merge. Fusion-Task-Id: FN-5257
This commit is contained in:
committed by
gsxdsm
parent
dd6ccc67a2
commit
69c77fa3bf
@@ -1138,6 +1138,44 @@ describe("Scheduler", () => {
|
||||
expect(auditCalls[0]?.[0]?.metadata?.bindingGates).toEqual(["maxConcurrent"]);
|
||||
});
|
||||
|
||||
it("dedupes queued-concurrency logs when only non-binding semaphore counts change", async () => {
|
||||
vi.mocked(existsSync).mockReturnValue(true);
|
||||
vi.mocked(readFile).mockResolvedValue("# Task\nDo something");
|
||||
|
||||
const semaphore = new AgentSemaphore(40);
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-A", column: "in-progress" }),
|
||||
createMockTask({ id: "FN-B", column: "in-progress" }),
|
||||
createMockTask({ id: "FN-C", column: "todo", dependencies: [] }),
|
||||
createMockTask({ id: "FN-D", column: "todo", dependencies: [] }),
|
||||
];
|
||||
const store = createMockStore({
|
||||
listTasks: vi.fn().mockResolvedValue(tasks),
|
||||
getSettings: vi.fn().mockResolvedValue({ maxConcurrent: 15, maxWorktrees: 3 }),
|
||||
});
|
||||
|
||||
const scheduler = new Scheduler(store, { semaphore });
|
||||
(scheduler as any).running = true;
|
||||
|
||||
await semaphore.acquire();
|
||||
await semaphore.acquire();
|
||||
await scheduler.schedule();
|
||||
semaphore.release();
|
||||
semaphore.release();
|
||||
await scheduler.schedule();
|
||||
|
||||
const concurrencyReasonCalls = (store.logEntry as ReturnType<typeof vi.fn>).mock.calls.filter(
|
||||
(call: unknown[]) => call[0] === "FN-D" && String(call[1]).includes("queued — concurrency limit reached"),
|
||||
);
|
||||
expect(concurrencyReasonCalls).toHaveLength(1);
|
||||
|
||||
const auditCalls = (store.recordRunAuditEvent as ReturnType<typeof vi.fn>).mock.calls.filter(
|
||||
(call: unknown[]) => (call[0] as { mutationType?: string } | undefined)?.mutationType === "scheduler:dispatch-queued-concurrency",
|
||||
);
|
||||
expect(auditCalls).toHaveLength(1);
|
||||
expect(auditCalls[0]?.[0]?.metadata?.bindingGates).toEqual(["maxWorktrees"]);
|
||||
});
|
||||
|
||||
it("re-logs and re-audits when binding gate changes", async () => {
|
||||
vi.mocked(existsSync).mockReturnValue(true);
|
||||
vi.mocked(readFile).mockResolvedValue("# Task\nDo something");
|
||||
|
||||
@@ -189,6 +189,24 @@ function formatConcurrencyLimitReason(diagnostic: ConcurrencyGateDiagnostic): st
|
||||
return `queued — concurrency limit reached: gate=${gateLabel}; ${details.join("; ")}`;
|
||||
}
|
||||
|
||||
function formatConcurrencyLimitMemoKey(diagnostic: ConcurrencyGateDiagnostic): string {
|
||||
const gates = diagnostic.bindingGates.join(",");
|
||||
const gateDetails = diagnostic.bindingGates.map((gate) => {
|
||||
const snapshot = gate === "maxConcurrent"
|
||||
? diagnostic.maxConcurrentGate
|
||||
: gate === "maxWorktrees"
|
||||
? diagnostic.maxWorktreesGate
|
||||
: diagnostic.semaphoreGate;
|
||||
const holders = diagnostic.holders[gate];
|
||||
const holderKey = holders && holders.length > 0 ? holders.join(",") : "none";
|
||||
if (!snapshot) {
|
||||
return `${gate}:missing:${holderKey}`;
|
||||
}
|
||||
return `${gate}:${snapshot.used}/${snapshot.limit}:${holderKey}`;
|
||||
});
|
||||
return `queued-concurrency:${gates}:${gateDetails.join("|")}`;
|
||||
}
|
||||
|
||||
export interface SchedulerOptions {
|
||||
/** Max concurrent in-progress tasks. Default: 2 */
|
||||
maxConcurrent?: number;
|
||||
@@ -509,6 +527,8 @@ export class Scheduler {
|
||||
this.failedTaskIds.delete(task.id);
|
||||
this.wasNodeDispatchValidationBlocked.delete(task.id);
|
||||
this.wasNodeBlocked.delete(task.id);
|
||||
this.wasPermanentAgentUnavailable.delete(task.id);
|
||||
this.clearDispatchQueuedReasonMemo(task.id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -602,8 +622,8 @@ export class Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
private async logDispatchQueuedReason(taskId: string, reason: string): Promise<boolean> {
|
||||
const key = `${taskId}:${reason}`;
|
||||
private async logDispatchQueuedReason(taskId: string, reason: string, memoKey?: string): Promise<boolean> {
|
||||
const key = `${taskId}:${memoKey ?? reason}`;
|
||||
if (this.wasDispatchQueuedReasonLogged.has(key)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1137,7 +1157,11 @@ export class Scheduler {
|
||||
// Dependencies met — check concurrency
|
||||
if (started >= available) {
|
||||
const reason = formatConcurrencyLimitReason(concurrencyGateDiagnostic);
|
||||
const didLog = await this.logDispatchQueuedReason(task.id, reason);
|
||||
const didLog = await this.logDispatchQueuedReason(
|
||||
task.id,
|
||||
reason,
|
||||
formatConcurrencyLimitMemoKey(concurrencyGateDiagnostic),
|
||||
);
|
||||
if (didLog) {
|
||||
await this.emitDispatchQueuedConcurrencyAudit(task, concurrencyGateDiagnostic);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user