storkit: merge 366_story_bot_sends_shutdown_message_on_server_stop_or_rebuild
This commit is contained in:
@@ -24,6 +24,7 @@ use crate::http::build_routes;
|
||||
use crate::http::context::AppContext;
|
||||
use crate::http::{remove_port_file, resolve_port, write_port_file};
|
||||
use crate::io::fs::find_story_kit_root;
|
||||
use crate::rebuild::{BotShutdownNotifier, ShutdownReason};
|
||||
use crate::state::SessionState;
|
||||
use crate::store::JsonFileStore;
|
||||
use crate::workflow::WorkflowState;
|
||||
@@ -177,17 +178,6 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
let startup_reconciliation_tx = reconciliation_tx.clone();
|
||||
// Clone for shutdown cleanup — kill orphaned PTY children before exiting.
|
||||
let agents_for_shutdown = Arc::clone(&agents);
|
||||
let ctx = AppContext {
|
||||
state: app_state,
|
||||
store,
|
||||
workflow,
|
||||
agents,
|
||||
watcher_tx,
|
||||
reconciliation_tx,
|
||||
perm_tx,
|
||||
perm_rx,
|
||||
qa_app_process: Arc::new(std::sync::Mutex::new(None)),
|
||||
};
|
||||
|
||||
// Build WhatsApp webhook context if bot.toml configures transport = "whatsapp".
|
||||
let whatsapp_ctx: Option<Arc<whatsapp::WhatsAppWebhookContext>> = startup_root
|
||||
@@ -255,7 +245,50 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
})
|
||||
});
|
||||
|
||||
let app = build_routes(ctx, whatsapp_ctx, slack_ctx);
|
||||
// Build a best-effort shutdown notifier for webhook-based transports.
|
||||
//
|
||||
// • Slack: channels are fixed at startup (channel_ids from bot.toml).
|
||||
// • WhatsApp: active senders are tracked at runtime in ambient_rooms.
|
||||
// We keep the WhatsApp context Arc so we can read the rooms at shutdown.
|
||||
// • Matrix: the bot task manages its own announcement via matrix_shutdown_tx.
|
||||
let bot_shutdown_notifier: Option<Arc<BotShutdownNotifier>> =
|
||||
if let Some(ref ctx) = slack_ctx {
|
||||
let channels: Vec<String> = ctx.channel_ids.iter().cloned().collect();
|
||||
Some(Arc::new(BotShutdownNotifier::new(
|
||||
Arc::clone(&ctx.transport) as Arc<dyn crate::transport::ChatTransport>,
|
||||
channels,
|
||||
ctx.bot_name.clone(),
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Retain a reference to the WhatsApp context for shutdown notifications.
|
||||
// At shutdown time we read ambient_rooms to get the current set of active senders.
|
||||
let whatsapp_ctx_for_shutdown: Option<Arc<whatsapp::WhatsAppWebhookContext>> =
|
||||
whatsapp_ctx.clone();
|
||||
|
||||
// Watch channel: signals the Matrix bot task to send a shutdown announcement.
|
||||
// `None` initial value means "server is running".
|
||||
let (matrix_shutdown_tx, matrix_shutdown_rx) =
|
||||
tokio::sync::watch::channel::<Option<ShutdownReason>>(None);
|
||||
let matrix_shutdown_tx = Arc::new(matrix_shutdown_tx);
|
||||
let matrix_shutdown_tx_for_rebuild = Arc::clone(&matrix_shutdown_tx);
|
||||
|
||||
let ctx = AppContext {
|
||||
state: app_state,
|
||||
store,
|
||||
workflow,
|
||||
agents,
|
||||
watcher_tx,
|
||||
reconciliation_tx,
|
||||
perm_tx,
|
||||
perm_rx,
|
||||
qa_app_process: Arc::new(std::sync::Mutex::new(None)),
|
||||
bot_shutdown: bot_shutdown_notifier.clone(),
|
||||
matrix_shutdown_tx: Some(Arc::clone(&matrix_shutdown_tx)),
|
||||
};
|
||||
|
||||
let app = build_routes(ctx, whatsapp_ctx.clone(), slack_ctx.clone());
|
||||
|
||||
// Optional Matrix bot: connect to the homeserver and start listening for
|
||||
// messages if `.storkit/bot.toml` is present and enabled.
|
||||
@@ -265,7 +298,11 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
watcher_tx_for_bot,
|
||||
perm_rx_for_bot,
|
||||
Arc::clone(&startup_agents),
|
||||
matrix_shutdown_rx,
|
||||
);
|
||||
} else {
|
||||
// Keep the receiver alive (drop it) so the sender never errors.
|
||||
drop(matrix_shutdown_rx);
|
||||
}
|
||||
|
||||
// On startup:
|
||||
@@ -295,6 +332,36 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
|
||||
let result = Server::new(TcpListener::bind(&addr)).run(app).await;
|
||||
|
||||
// ── Shutdown notifications (best-effort) ─────────────────────────────
|
||||
//
|
||||
// The server is stopping (SIGINT / SIGTERM). Notify active bot channels
|
||||
// so participants know the bot is going offline. We do this before killing
|
||||
// PTY children so network I/O can still complete.
|
||||
|
||||
// Slack: notifier holds the fixed channel list.
|
||||
if let Some(ref notifier) = bot_shutdown_notifier {
|
||||
notifier.notify(ShutdownReason::Manual).await;
|
||||
}
|
||||
|
||||
// WhatsApp: read the current set of ambient rooms and notify each sender.
|
||||
if let Some(ref ctx) = whatsapp_ctx_for_shutdown {
|
||||
let rooms: Vec<String> = ctx.ambient_rooms.lock().unwrap().iter().cloned().collect();
|
||||
if !rooms.is_empty() {
|
||||
let wa_notifier = BotShutdownNotifier::new(
|
||||
Arc::clone(&ctx.transport) as Arc<dyn crate::transport::ChatTransport>,
|
||||
rooms,
|
||||
ctx.bot_name.clone(),
|
||||
);
|
||||
wa_notifier.notify(ShutdownReason::Manual).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Matrix: signal the bot task and give it a short window to send its message.
|
||||
let _ = matrix_shutdown_tx_for_rebuild.send(Some(ShutdownReason::Manual));
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1500)).await;
|
||||
|
||||
// ── Cleanup ──────────────────────────────────────────────────────────
|
||||
|
||||
// Kill all active PTY child processes before exiting to prevent orphaned
|
||||
// Claude Code processes from running after the server restarts.
|
||||
agents_for_shutdown.kill_all_children();
|
||||
|
||||
Reference in New Issue
Block a user