trail-boss/daemon/types.ts
jedarden b15d61b0a8 docs(tb-4wur): document normalized event schema for stuck/unstuck
- Add comprehensive schema documentation in docs/notes/normalized-event-schema.md
- Define all four event types: StuckEvent, UnstuckEvent, SessionRegistered, SessionEnded
- Specify required/optional fields, data types, validation rules for each event type
- Add examples for each event type from different sources (Claude Code hooks, tmux detector)
- Document type guards, error handling, and usage patterns
- Enhance code comments in daemon/types.ts with validation rules and references
- Update tmux-detector.ts NormalizedEvent type to match stricter type definitions

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-02 22:17:40 -04:00

155 lines
5.1 KiB
TypeScript

// Normalized event types for the harness-agnostic adapter contract
//
// This design isolates harness coupling to the adapter layer — the daemon consumes
// only normalized events and has no knowledge of harness-specific formats.
//
// Full schema documentation: docs/notes/normalized-event-schema.md
// Design rationale: docs/notes/decisions.md (section "Normalized event API endpoint")
// ============================================================================
// Raw Hook Events (Claude Code-specific)
// ============================================================================
// Raw hook events from Claude Code (what the emitter POSTs)
export interface HookEvent {
session_id: string;
transcript_path: string;
cwd: string;
hook_event_name: "Stop" | "PermissionRequest" | "UserPromptSubmit" | "SessionStart" | "SessionEnd";
permission_mode?: string;
effort?: { level: string };
// Stop-specific
last_assistant_message?: string;
stop_hook_active?: boolean;
background_tasks?: string[];
session_crons?: string[];
// PermissionRequest-specific
tool_name?: string;
tool_input?: unknown;
permission_suggestions?: Array<{ type: string; mode: string; destination: string }>;
}
// ============================================================================
// Normalized Events (harness-agnostic)
// ============================================================================
// Type union for all normalized events
export type NormalizedEvent = StuckEvent | UnstuckEvent | SessionRegistered | SessionEnded;
// The normalized stuck/unstuck event that the daemon consumes
// This isolates harness coupling to the adapter layer
//
// All normalized events have a `type` discriminator for use with type guards.
// The /event/normalized endpoint accepts these pre-normalized events directly
// from harness-specific adapters (Claude Code hooks, tmux detector, etc.).
/**
* StuckEvent — A session became stuck and needs attention.
*
* This is the core event that adds a session to the queue. When a session hits
* a Stop hook or PermissionRequest, the adapter emits this event to queue the
* session for operator attention.
*
* Validation rules:
* - type: Must be exactly "stuck"
* - sessionId: Non-empty string, unique identifier
* - paneId: Non-empty string, tmux pane ID (e.g., "%446")
* - cwd: String, may be empty for synthetic sessions
* - transcriptPath: String, may be empty for synthetic sessions
* - reason: Must be "stopped" or "permission"
* - message: Non-empty string, human-readable context
* - timestamp: Positive number, Unix milliseconds
*
* See: docs/notes/normalized-event-schema.md#1-stuckevent
*/
export interface StuckEvent {
type: "stuck";
sessionId: string;
paneId: string;
cwd: string;
transcriptPath: string;
reason: "stopped" | "permission";
message: string; // last_assistant_message or tool_name+input
timestamp: number; // unix ms
}
/**
* UnstuckEvent — A session progressed and is no longer stuck.
*
* This removes the session from the queue. When a session resumes (user
* submits a prompt, or permission is granted), the adapter emits this event.
*
* Validation rules:
* - type: Must be exactly "unstuck"
* - sessionId: Must match a stuck session in the queue (ignored otherwise)
* - timestamp: Positive number, Unix milliseconds
*
* See: docs/notes/normalized-event-schema.md#2-unstuckevent
*/
export interface UnstuckEvent {
type: "unstuck";
sessionId: string;
timestamp: number;
}
/**
* SessionRegistered — A new session has been registered for monitoring.
*
* This initializes session tracking. The adapter emits this when a new session
* is discovered (e.g., SessionStart hook, or tmux pane with @tb- prefix).
*
* Validation rules:
* - type: Must be exactly "registered"
* - sessionId: Non-empty string, unique identifier
* - paneId: Non-empty string, tmux pane ID
* - cwd: String, may be empty
* - transcriptPath: String, may be empty
* - timestamp: Positive number, Unix milliseconds
*
* See: docs/notes/normalized-event-schema.md#3-sessionregistered
*/
export interface SessionRegistered {
type: "registered";
sessionId: string;
paneId: string;
cwd: string;
transcriptPath: string;
timestamp: number;
}
/**
* SessionEnded — A session has terminated.
*
* This removes the session from tracking and emits an unstuck event if the
* session was stuck. The adapter emits this when a session ends (e.g., SessionEnd
* hook, or tmux pane closed).
*
* Validation rules:
* - type: Must be exactly "ended"
* - sessionId: Must match a tracked session (ignored otherwise)
* - timestamp: Positive number, Unix milliseconds
*
* See: docs/notes/normalized-event-schema.md#4-sessionended
*/
export interface SessionEnded {
type: "ended";
sessionId: string;
timestamp: number;
}
// Queue state
export interface QueueItem {
id: number;
sessionId: string;
paneId: string;
cwd: string;
reason: "stopped" | "permission";
message: string;
stuckAt: number;
skipCooldownUntil: number | null;
}
export interface NextResponse {
paneId: string | null; // null if queue empty or all on cooldown
reason: string | null; // if null, why empty
}