fix(FN-2036): wire agent reflections runtime and UI dependencies

- Add a reusable .btn-secondary style (including light-theme hover/focus states) for reflections tab actions
- Initialize ReflectionStore in reflection API routes before reading or writing reflections
- Create AgentStore and ReflectionStore during in-process runtime startup and wire AgentReflectionService into TaskExecutor
- Reuse the initialized AgentStore for HeartbeatMonitor and trigger scheduler setup with non-fatal guards
This commit is contained in:
Fusion
2026-04-17 22:29:16 -07:00
committed by gsxdsm
parent 3e095c1d22
commit cfffe156b2
3 changed files with 82 additions and 12 deletions

View File

@@ -1192,6 +1192,22 @@ body {
box-shadow: var(--glow-warning);
}
.btn-secondary {
background: transparent;
border: 1px solid var(--border);
color: var(--text);
}
.btn-secondary:hover {
background: var(--card-hover);
border-color: var(--text-muted);
}
.btn-secondary:active {
transform: scale(0.97);
}
.btn-secondary:focus-visible {
box-shadow: var(--focus-ring-strong);
}
.btn-sm {
padding: 4px 10px;
font-size: 12px;
@@ -10841,6 +10857,16 @@ html .column.drag-over * {
box-shadow: var(--glow-warning);
}
[data-theme="light"] .btn-secondary {
background: transparent;
border: 1px solid var(--border);
color: var(--text);
}
[data-theme="light"] .btn-secondary:hover {
background: var(--card-hover);
border-color: var(--text-muted);
}
/* ============================================================
THEME SELECTOR COMPONENT STYLES
============================================================ */

View File

@@ -13016,6 +13016,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
const agentStore = new AgentStore({ rootDir: scopedStore.getFusionDir() });
const reflectionStore = new ReflectionStore({ rootDir: scopedStore.getFusionDir() });
await agentStore.init();
await reflectionStore.init();
const agentId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
if (!agentId) {
@@ -13056,6 +13057,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
const agentStore = new AgentStore({ rootDir: scopedStore.getFusionDir() });
const reflectionStore = new ReflectionStore({ rootDir: scopedStore.getFusionDir() });
await agentStore.init();
await reflectionStore.init();
const agentId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
if (!agentId) {
@@ -13097,6 +13099,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
const agentStore = new AgentStore({ rootDir: taskStore.getRootDir() });
const reflectionStore = new ReflectionStore({ rootDir: taskStore.getRootDir() });
await agentStore.init();
await reflectionStore.init();
const agentId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
if (!agentId) {
@@ -13142,6 +13145,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
const agentStore = new AgentStore({ rootDir: scopedStore.getFusionDir() });
const reflectionStore = new ReflectionStore({ rootDir: scopedStore.getFusionDir() });
await agentStore.init();
await reflectionStore.init();
const agentId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
if (!agentId) {
@@ -13182,6 +13186,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
const agentStore = new AgentStore({ rootDir: taskStore.getRootDir() });
const reflectionStore = new ReflectionStore({ rootDir: taskStore.getRootDir() });
await agentStore.init();
await reflectionStore.init();
const agentId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
if (!agentId) {

View File

@@ -254,7 +254,7 @@ export class InProcessRuntime
},
});
// 5. Initialize TaskExecutor
// 5b. Initialize TaskExecutor
this.stuckTaskDetector = new StuckTaskDetector(this.taskStore, {
beforeRequeue: (taskId) => this.selfHealingManager?.checkStuckBudget(taskId) ?? Promise.resolve(true),
onLoopDetected: (event) => this.executor?.handleLoopDetected(event) ?? Promise.resolve(false),
@@ -268,6 +268,46 @@ export class InProcessRuntime
},
});
// 5a. Initialize AgentStore (required for reflection service and heartbeat monitoring)
let agentStoreForReflection: import("@fusion/core").AgentStore | undefined;
try {
const { AgentStore: AgentStoreClass } = await import("@fusion/core");
agentStoreForReflection = new AgentStoreClass({ rootDir: this.taskStore.getFusionDir() });
await agentStoreForReflection.init();
runtimeLog.log("AgentStore initialized for reflection service");
} catch (agentErr) {
runtimeLog.warn(`AgentStore initialization failed (reflection service will be unavailable):`, agentErr instanceof Error ? agentErr.message : agentErr);
}
this.agentStore = agentStoreForReflection;
// 5b. Initialize ReflectionStore for agent reflections
let reflectionStoreForService: import("@fusion/core").ReflectionStore | undefined;
try {
const { ReflectionStore: ReflectionStoreClass } = await import("@fusion/core");
reflectionStoreForService = new ReflectionStoreClass({ rootDir: this.taskStore.getFusionDir() });
await reflectionStoreForService.init();
runtimeLog.log("ReflectionStore initialized for reflection service");
} catch (reflErr) {
runtimeLog.warn(`ReflectionStore initialization failed (reflection service will be unavailable):`, reflErr instanceof Error ? reflErr.message : reflErr);
}
// 5c. Initialize AgentReflectionService (requires agentStore and reflectionStore)
let reflectionService: import("@fusion/engine").AgentReflectionService | undefined;
if (agentStoreForReflection && reflectionStoreForService) {
try {
const { AgentReflectionService: AgentReflectionServiceClass } = await import("@fusion/engine");
reflectionService = new AgentReflectionServiceClass({
agentStore: agentStoreForReflection,
taskStore: this.taskStore,
reflectionStore: reflectionStoreForService,
rootDir: this.config.workingDirectory,
});
runtimeLog.log("AgentReflectionService initialized");
} catch (reflServiceErr) {
runtimeLog.warn(`AgentReflectionService initialization failed:`, reflServiceErr instanceof Error ? reflServiceErr.message : reflServiceErr);
}
}
const executorOptions: TaskExecutorOptions = {
semaphore: this.globalSemaphore,
pool: this.worktreePool,
@@ -276,6 +316,7 @@ export class InProcessRuntime
pluginRunner: this.pluginRunner,
messageStore: this.messageStore,
missionStore,
reflectionService,
onSliceComplete: (slice) => {
void this.scheduler.onSliceComplete(slice);
},
@@ -363,12 +404,11 @@ export class InProcessRuntime
executorOptions
);
// 6. Initialize AgentStore and HeartbeatMonitor
try {
const { AgentStore: AgentStoreClass } = await import("@fusion/core");
this.agentStore = new AgentStoreClass({ rootDir: this.taskStore.getFusionDir() });
await this.agentStore.init();
// 6. Initialize HeartbeatMonitor (reuses AgentStore from step 5a)
if (this.heartbeatMonitor) {
// Already started — nothing to do
}
if (!this.heartbeatMonitor && this.agentStore) {
this.heartbeatMonitor = new HeartbeatMonitor({
store: this.agentStore,
agentStore: this.agentStore, // enables per-agent config resolution
@@ -383,8 +423,10 @@ export class InProcessRuntime
},
});
this.heartbeatMonitor.start();
}
// Initialize HeartbeatTriggerScheduler
// 6a. Initialize HeartbeatTriggerScheduler (only if agentStore is available)
if (this.agentStore) {
this.triggerScheduler = new HeartbeatTriggerScheduler(
this.agentStore,
async (agentId, source, context: WakeContext) => {
@@ -465,10 +507,7 @@ export class InProcessRuntime
runtimeLog.warn(`Failed to register agents for heartbeat triggers:`, regErr);
}
runtimeLog.log(`AgentStore, HeartbeatMonitor, and TriggerScheduler initialized`);
} catch (agentErr) {
// Non-fatal — agent monitoring is optional
runtimeLog.warn(`AgentStore initialization failed (continuing without agent monitoring):`, agentErr);
runtimeLog.log(`HeartbeatMonitor and TriggerScheduler initialized`);
}
// 7. Initialize TriageProcessor (task specification)