mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
feat: implement notifications and event history features
- Added Notification Service to manage project-level notifications, including creation, listing, marking as read, and dismissing notifications. - Introduced Event History Service to store and manage historical events, allowing for listing, retrieval, deletion, and replaying of events. - Integrated notifications into the server and UI, providing real-time updates for feature statuses and operations. - Enhanced sidebar and project switcher components to display unread notifications count. - Created dedicated views for managing notifications and event history, improving user experience and accessibility. These changes enhance the application's ability to inform users about important events and statuses, improving overall usability and responsiveness.
This commit is contained in:
@@ -19,6 +19,12 @@ export {
|
||||
getAppSpecPath,
|
||||
getBranchTrackingPath,
|
||||
getExecutionStatePath,
|
||||
getNotificationsPath,
|
||||
// Event history paths
|
||||
getEventHistoryDir,
|
||||
getEventHistoryIndexPath,
|
||||
getEventPath,
|
||||
ensureEventHistoryDir,
|
||||
ensureAutomakerDir,
|
||||
getGlobalSettingsPath,
|
||||
getCredentialsPath,
|
||||
|
||||
@@ -161,6 +161,18 @@ export function getAppSpecPath(projectPath: string): string {
|
||||
return path.join(getAutomakerDir(projectPath), 'app_spec.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notifications file path for a project
|
||||
*
|
||||
* Stores project-level notifications for feature status changes and operation completions.
|
||||
*
|
||||
* @param projectPath - Absolute path to project directory
|
||||
* @returns Absolute path to {projectPath}/.automaker/notifications.json
|
||||
*/
|
||||
export function getNotificationsPath(projectPath: string): string {
|
||||
return path.join(getAutomakerDir(projectPath), 'notifications.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the branch tracking file path for a project
|
||||
*
|
||||
@@ -335,6 +347,57 @@ export async function ensureIdeationDir(projectPath: string): Promise<string> {
|
||||
return ideationDir;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Event History Paths
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get the event history directory for a project
|
||||
*
|
||||
* Contains stored event records for debugging and replay.
|
||||
*
|
||||
* @param projectPath - Absolute path to project directory
|
||||
* @returns Absolute path to {projectPath}/.automaker/events
|
||||
*/
|
||||
export function getEventHistoryDir(projectPath: string): string {
|
||||
return path.join(getAutomakerDir(projectPath), 'events');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event history index file path
|
||||
*
|
||||
* Stores an index of all events for quick listing without scanning directory.
|
||||
*
|
||||
* @param projectPath - Absolute path to project directory
|
||||
* @returns Absolute path to {projectPath}/.automaker/events/index.json
|
||||
*/
|
||||
export function getEventHistoryIndexPath(projectPath: string): string {
|
||||
return path.join(getEventHistoryDir(projectPath), 'index.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file path for a specific event
|
||||
*
|
||||
* @param projectPath - Absolute path to project directory
|
||||
* @param eventId - Event identifier
|
||||
* @returns Absolute path to {projectPath}/.automaker/events/{eventId}.json
|
||||
*/
|
||||
export function getEventPath(projectPath: string, eventId: string): string {
|
||||
return path.join(getEventHistoryDir(projectPath), `${eventId}.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the event history directory for a project if it doesn't exist
|
||||
*
|
||||
* @param projectPath - Absolute path to project directory
|
||||
* @returns Promise resolving to the created events directory path
|
||||
*/
|
||||
export async function ensureEventHistoryDir(projectPath: string): Promise<string> {
|
||||
const eventsDir = getEventHistoryDir(projectPath);
|
||||
await secureFs.mkdir(eventsDir, { recursive: true });
|
||||
return eventsDir;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Global Settings Paths (stored in DATA_DIR from app.getPath('userData'))
|
||||
// ============================================================================
|
||||
|
||||
123
libs/types/src/event-history.ts
Normal file
123
libs/types/src/event-history.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Event History Types - Stored event records for debugging and replay
|
||||
*
|
||||
* Events are stored on disk to allow users to:
|
||||
* - View historical events for debugging
|
||||
* - Replay events with custom hooks
|
||||
* - Test hook configurations against past events
|
||||
*/
|
||||
|
||||
import type { EventHookTrigger } from './settings.js';
|
||||
|
||||
/**
|
||||
* StoredEvent - A single event record stored on disk
|
||||
*
|
||||
* Contains all information needed to replay the event or inspect what happened.
|
||||
*/
|
||||
export interface StoredEvent {
|
||||
/** Unique identifier for this event record */
|
||||
id: string;
|
||||
/** The hook trigger type this event maps to */
|
||||
trigger: EventHookTrigger;
|
||||
/** ISO timestamp when the event occurred */
|
||||
timestamp: string;
|
||||
/** ID of the feature involved (if applicable) */
|
||||
featureId?: string;
|
||||
/** Name of the feature involved (if applicable) */
|
||||
featureName?: string;
|
||||
/** Path to the project where the event occurred */
|
||||
projectPath: string;
|
||||
/** Name of the project (extracted from path) */
|
||||
projectName: string;
|
||||
/** Error message if this was an error event */
|
||||
error?: string;
|
||||
/** Error classification if applicable */
|
||||
errorType?: string;
|
||||
/** Whether the feature passed (for completion events) */
|
||||
passes?: boolean;
|
||||
/** Additional context/metadata for the event */
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* StoredEventIndex - Quick lookup index for event history
|
||||
*
|
||||
* Stored separately for fast listing without loading full event data.
|
||||
*/
|
||||
export interface StoredEventIndex {
|
||||
/** Version for future migrations */
|
||||
version: number;
|
||||
/** Array of event summaries for quick listing */
|
||||
events: StoredEventSummary[];
|
||||
}
|
||||
|
||||
/**
|
||||
* StoredEventSummary - Minimal event info for listing
|
||||
*/
|
||||
export interface StoredEventSummary {
|
||||
/** Event ID */
|
||||
id: string;
|
||||
/** Trigger type */
|
||||
trigger: EventHookTrigger;
|
||||
/** When it occurred */
|
||||
timestamp: string;
|
||||
/** Feature name for display (if applicable) */
|
||||
featureName?: string;
|
||||
/** Feature ID (if applicable) */
|
||||
featureId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* EventHistoryFilter - Options for filtering event history
|
||||
*/
|
||||
export interface EventHistoryFilter {
|
||||
/** Filter by trigger type */
|
||||
trigger?: EventHookTrigger;
|
||||
/** Filter by feature ID */
|
||||
featureId?: string;
|
||||
/** Filter events after this timestamp */
|
||||
since?: string;
|
||||
/** Filter events before this timestamp */
|
||||
until?: string;
|
||||
/** Maximum number of events to return */
|
||||
limit?: number;
|
||||
/** Number of events to skip (for pagination) */
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* EventReplayResult - Result of replaying an event
|
||||
*/
|
||||
export interface EventReplayResult {
|
||||
/** Event that was replayed */
|
||||
eventId: string;
|
||||
/** Number of hooks that were triggered */
|
||||
hooksTriggered: number;
|
||||
/** Results from each hook execution */
|
||||
hookResults: EventReplayHookResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* EventReplayHookResult - Result of a single hook execution during replay
|
||||
*/
|
||||
export interface EventReplayHookResult {
|
||||
/** Hook ID */
|
||||
hookId: string;
|
||||
/** Hook name (if set) */
|
||||
hookName?: string;
|
||||
/** Whether the hook executed successfully */
|
||||
success: boolean;
|
||||
/** Error message if failed */
|
||||
error?: string;
|
||||
/** Execution time in milliseconds */
|
||||
durationMs: number;
|
||||
}
|
||||
|
||||
/** Current version of the event history index schema */
|
||||
export const EVENT_HISTORY_VERSION = 1;
|
||||
|
||||
/** Default empty event history index */
|
||||
export const DEFAULT_EVENT_HISTORY_INDEX: StoredEventIndex = {
|
||||
version: EVENT_HISTORY_VERSION,
|
||||
events: [],
|
||||
};
|
||||
@@ -10,6 +10,7 @@ export type EventType =
|
||||
| 'auto-mode:idle'
|
||||
| 'auto-mode:error'
|
||||
| 'backlog-plan:event'
|
||||
| 'feature:created'
|
||||
| 'feature:started'
|
||||
| 'feature:completed'
|
||||
| 'feature:stopped'
|
||||
@@ -45,6 +46,7 @@ export type EventType =
|
||||
| 'worktree:init-completed'
|
||||
| 'dev-server:started'
|
||||
| 'dev-server:output'
|
||||
| 'dev-server:stopped';
|
||||
| 'dev-server:stopped'
|
||||
| 'notification:created';
|
||||
|
||||
export type EventCallback = (type: EventType, payload: unknown) => void;
|
||||
|
||||
@@ -270,3 +270,18 @@ export type {
|
||||
IdeationStreamEvent,
|
||||
IdeationAnalysisEvent,
|
||||
} from './ideation.js';
|
||||
|
||||
// Notification types
|
||||
export type { NotificationType, Notification, NotificationsFile } from './notification.js';
|
||||
export { NOTIFICATIONS_VERSION, DEFAULT_NOTIFICATIONS_FILE } from './notification.js';
|
||||
|
||||
// Event history types
|
||||
export type {
|
||||
StoredEvent,
|
||||
StoredEventIndex,
|
||||
StoredEventSummary,
|
||||
EventHistoryFilter,
|
||||
EventReplayResult,
|
||||
EventReplayHookResult,
|
||||
} from './event-history.js';
|
||||
export { EVENT_HISTORY_VERSION, DEFAULT_EVENT_HISTORY_INDEX } from './event-history.js';
|
||||
|
||||
58
libs/types/src/notification.ts
Normal file
58
libs/types/src/notification.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Notification Types - Types for project-level notification system
|
||||
*
|
||||
* Notifications alert users when features reach specific statuses
|
||||
* or when long-running operations complete.
|
||||
*/
|
||||
|
||||
/**
|
||||
* NotificationType - Types of notifications that can be created
|
||||
*/
|
||||
export type NotificationType =
|
||||
| 'feature_waiting_approval'
|
||||
| 'feature_verified'
|
||||
| 'spec_regeneration_complete'
|
||||
| 'agent_complete';
|
||||
|
||||
/**
|
||||
* Notification - A single notification entry
|
||||
*/
|
||||
export interface Notification {
|
||||
/** Unique identifier for the notification */
|
||||
id: string;
|
||||
/** Type of notification */
|
||||
type: NotificationType;
|
||||
/** Short title for display */
|
||||
title: string;
|
||||
/** Longer descriptive message */
|
||||
message: string;
|
||||
/** ISO timestamp when notification was created */
|
||||
createdAt: string;
|
||||
/** Whether the notification has been read */
|
||||
read: boolean;
|
||||
/** Whether the notification has been dismissed */
|
||||
dismissed: boolean;
|
||||
/** Associated feature ID if applicable */
|
||||
featureId?: string;
|
||||
/** Project path this notification belongs to */
|
||||
projectPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationsFile - Structure of the notifications.json file
|
||||
*/
|
||||
export interface NotificationsFile {
|
||||
/** Version for future migrations */
|
||||
version: number;
|
||||
/** List of notifications */
|
||||
notifications: Notification[];
|
||||
}
|
||||
|
||||
/** Current version of the notifications file schema */
|
||||
export const NOTIFICATIONS_VERSION = 1;
|
||||
|
||||
/** Default notifications file structure */
|
||||
export const DEFAULT_NOTIFICATIONS_FILE: NotificationsFile = {
|
||||
version: NOTIFICATIONS_VERSION,
|
||||
notifications: [],
|
||||
};
|
||||
@@ -108,12 +108,14 @@ export type ModelProvider = 'claude' | 'cursor' | 'codex' | 'opencode';
|
||||
/**
|
||||
* EventHookTrigger - Event types that can trigger custom hooks
|
||||
*
|
||||
* - feature_created: A new feature was created
|
||||
* - feature_success: Feature completed successfully
|
||||
* - feature_error: Feature failed with an error
|
||||
* - auto_mode_complete: Auto mode finished processing all features
|
||||
* - auto_mode_error: Auto mode encountered a critical error and paused
|
||||
*/
|
||||
export type EventHookTrigger =
|
||||
| 'feature_created'
|
||||
| 'feature_success'
|
||||
| 'feature_error'
|
||||
| 'auto_mode_complete'
|
||||
@@ -186,6 +188,7 @@ export interface EventHook {
|
||||
|
||||
/** Human-readable labels for event hook triggers */
|
||||
export const EVENT_HOOK_TRIGGER_LABELS: Record<EventHookTrigger, string> = {
|
||||
feature_created: 'Feature created',
|
||||
feature_success: 'Feature completed successfully',
|
||||
feature_error: 'Feature failed with error',
|
||||
auto_mode_complete: 'Auto mode completed all features',
|
||||
@@ -298,6 +301,8 @@ export interface KeyboardShortcuts {
|
||||
settings: string;
|
||||
/** Open terminal */
|
||||
terminal: string;
|
||||
/** Open notifications */
|
||||
notifications: string;
|
||||
/** Toggle sidebar visibility */
|
||||
toggleSidebar: string;
|
||||
/** Add new feature */
|
||||
@@ -800,6 +805,7 @@ export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = {
|
||||
context: 'C',
|
||||
settings: 'S',
|
||||
terminal: 'T',
|
||||
notifications: 'X',
|
||||
toggleSidebar: '`',
|
||||
addFeature: 'N',
|
||||
addContextFile: 'N',
|
||||
|
||||
Reference in New Issue
Block a user