Skip to main content

midtown/
lib.rs

1//! Midtown - Multi-agent workspace management daemon for Gas Town.
2//!
3//! This crate provides the core library for the Midtown daemon, which manages
4//! multiple agent workspaces (polecats, refineries, witnesses) in a Git-based
5//! workflow system.
6//!
7//! ## Core Components
8//!
9//! - **RPC**: JSON-RPC 2.0 protocol for inter-process communication
10//! - **Channels**: Append-only message logs for agent coordination
11//! - **Cursors**: Per-agent position tracking in message streams
12//! - **Worktrees**: Git worktree isolation for coworkers
13//! - **Coworkers**: Agent session management via tmux
14//! - **Tmux**: Low-level tmux session operations
15//!
16//! ## Quick Start
17//!
18//! The primary abstractions are [`Channel`] for communication and [`Message`]
19//! for individual messages:
20//!
21//! ```
22//! # use tempfile::TempDir;
23//! use midtown::{Channel, Message, MessageType};
24//!
25//! # let temp_dir = TempDir::new().unwrap();
26//! // Create a channel for agent communication
27//! let channel = Channel::new(temp_dir.path(), "midtown").unwrap();
28//!
29//! // Agents send messages to the channel
30//! channel.send(&Message::text("lead", "Starting build")).unwrap();
31//! channel.send(&Message::status("worker1", "Compiling...")).unwrap();
32//! channel.send(&Message::text("worker1", "Build complete")).unwrap();
33//!
34//! // Each agent tracks their read position with cursors
35//! let messages = channel.read_since_cursor("worker2").unwrap();
36//! assert_eq!(messages.len(), 3);
37//!
38//! // Subsequent reads only return new messages
39//! channel.send(&Message::text("lead", "Deploy now")).unwrap();
40//! let new_messages = channel.read_since_cursor("worker2").unwrap();
41//! assert_eq!(new_messages.len(), 1);
42//! ```
43
44// Daemon server
45pub mod daemon;
46
47// Pure decision functions and shared types for the daemon tick loop
48pub mod rules;
49
50// Pane content pattern detection (usage limits, API errors, UI chrome)
51pub mod pane_detection;
52
53// RPC subsystem (furiosa)
54pub mod rpc;
55
56// Worktree management for coworker isolation (slit)
57pub mod worktree;
58
59// Channel management subsystem (nux)
60mod channel;
61mod cursor;
62mod message;
63
64// Coworker management (nux)
65pub mod coworker;
66pub mod tmux;
67
68// GitHub webhook integration (rictus)
69pub mod webhook;
70
71// Web server for Svelte mobile app
72pub mod web;
73
74// Standalone multi-project webserver
75pub mod webserver;
76
77// Project configuration
78pub mod config;
79
80// Structured coworker state reporting (replaces pane-content parsing for decisions)
81pub mod coworker_state;
82
83// Agent system prompts
84pub mod agents;
85
86// Claude Code task storage integration
87pub mod tasks;
88
89// Path utilities (socket paths, repo detection)
90pub mod paths;
91
92// Auth profile management for multi-account support
93pub mod auth;
94
95// Persistent GitHub state (PR reviewer assignments)
96pub mod github_state;
97
98// GitHub API rate limit tracking
99pub mod github_rate_limit;
100
101// Task-based worktree registry
102pub mod worktree_registry;
103
104// CI check duration statistics (for auto-retry of stale checks)
105pub mod ci_stats;
106
107// Reminder system (one-shot condition-based reminders)
108pub mod reminders;
109
110// Web Push notification support
111pub mod push;
112
113// Randomized daemon event messages
114pub mod daemon_messages;
115
116// Headless Claude Code executor (JSON streaming, no tmux)
117pub mod headless;
118
119// Unified launch configuration for Claude Code sessions (tmux + headless)
120pub mod launch;
121
122// Lightweight filesystem sandbox (sandbox-exec on macOS, bwrap on Linux)
123pub mod sandbox;
124
125// Agent teams mailbox writer (filesystem-based message delivery)
126pub mod mailbox;
127
128// Session key type for multi-session coworker identity
129pub mod session_key;
130
131// API usage data (session + weekly utilization from Anthropic OAuth API)
132pub mod usage;
133
134// AI channel clustering for task organization
135pub mod clustering;
136
137// Specialized headless coworker abstraction
138pub mod specialized;
139
140// Test utilities
141// Note: Always available for use in both library and binary tests.
142// The retry_with_backoff function is small and has no dependencies,
143// so there's no harm in including it in production builds.
144pub mod test_utils;
145
146pub use channel::{Channel, ChannelRouter};
147pub use coworker::{Coworker, CoworkerManager, CoworkerStatus, is_coworker_name};
148pub use cursor::Cursor;
149pub use message::{Message, MessageType};
150pub use session_key::SessionKey;
151pub use usage::{UsageData, fetch_usage_for_profile};
152pub use worktree::{WorktreeError, WorktreeInfo, WorktreeManager};
153
154/// Resolve the `web-app/dist/` directory containing built static assets.
155///
156/// Checks candidates in order and returns the first that exists:
157/// 1. Next to the running executable (`exe_dir/web-app/dist`)
158/// 2. In the source tree where the binary was compiled (`CARGO_MANIFEST_DIR/web-app/dist`)
159///
160/// Falls back to the source-tree path even if it doesn't exist, so callers
161/// get a meaningful path for error messages.
162pub fn resolve_web_dir() -> std::path::PathBuf {
163    // Candidate 1: next to the executable (works for bundled installs)
164    if let Some(exe_dir) = std::env::current_exe()
165        .ok()
166        .and_then(|p| p.parent().map(|p| p.to_path_buf()))
167    {
168        let candidate = exe_dir.join("web-app").join("dist");
169        if candidate.exists() {
170            return candidate;
171        }
172    }
173
174    // Candidate 2: source tree where `cargo build` ran (baked in at compile time)
175    std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
176        .join("web-app")
177        .join("dist")
178}
179
180use thiserror::Error;
181
182/// Errors that can occur in the Midtown daemon.
183#[derive(Error, Debug)]
184pub enum Error {
185    /// I/O error (file, socket, etc.)
186    #[error("I/O error: {0}")]
187    Io(#[from] std::io::Error),
188
189    /// JSON serialization/deserialization error
190    #[error("JSON error: {0}")]
191    Json(#[from] serde_json::Error),
192
193    /// RPC protocol error
194    #[error("RPC error: {message} (code: {code})")]
195    Rpc { code: i32, message: String },
196
197    /// Channel not found
198    #[error("Channel not found: {0}")]
199    ChannelNotFound(String),
200
201    /// Invalid message format
202    #[error("Invalid message format: {0}")]
203    InvalidMessage(String),
204}
205
206/// Result type alias for Midtown operations.
207pub type Result<T> = std::result::Result<T, Error>;