mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-01 00:59:33 +00:00
Remove restart infrastructure, generalize WASM channel setup (#493)
* refactor: remove restart infrastructure and generalize Telegram-specific code Remove the gateway restart mechanism (hot-activation works, restart won't fix activation failures) and generalize Telegram-specific hardcoded checks so all WASM channels get equal treatment. Part 1 - Remove restart infrastructure: - Remove needs_restart from ActionResponse, restart_requested from GatewayState - Remove gateway_restart_handler, /api/gateway/restart route, exit code 75 - Remove restart overlay JS/CSS (dead code - restartGateway() never called) - Surface actual activation errors instead of suggesting restart Part 2 - Generalize Telegram-specific code: - Replace telegram_owner_id: Option<i64> with generic wasm_channel_owner_ids: HashMap<String, i64> (backwards-compatible via TELEGRAM_OWNER_ID env var) - Pairing status check now applies to all active WASM channels - All channels get 3-step stepper in web UI, remove "coming soon" note - Remove dead setup_telegram() code (~700 lines) - Telegram's capabilities.json declares required_secrets, so the generic setup_wasm_channel() path handles it Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test: add Settings::set() test for wasm_channel_owner_ids Addresses review feedback: verify that setting per-channel owner IDs via the dotted-path Settings::set() API works correctly with the new HashMap<String, i64> type. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(web): refresh extension stepper after pairing approval loadPairingRequests only refreshed the pairing section, not the stepper status. Call loadExtensions() instead so the stepper updates from "Awaiting Pairing" to "Active" immediately after approval. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
5f841554d5
commit
78878ad7ef
@@ -33,7 +33,7 @@ pub async fn extensions_list_handler(
|
||||
"failed".to_string()
|
||||
} else if !ext.authenticated {
|
||||
"installed".to_string()
|
||||
} else if ext.active && ext.name == "telegram" {
|
||||
} else if ext.active {
|
||||
let has_paired = pairing_store
|
||||
.read_allow_from(&ext.name)
|
||||
.map(|list| !list.is_empty())
|
||||
|
||||
@@ -94,7 +94,6 @@ impl GatewayChannel {
|
||||
registry_entries: Vec::new(),
|
||||
cost_guard: None,
|
||||
startup_time: std::time::Instant::now(),
|
||||
restart_requested: std::sync::atomic::AtomicBool::new(false),
|
||||
});
|
||||
|
||||
Self {
|
||||
@@ -128,7 +127,6 @@ impl GatewayChannel {
|
||||
registry_entries: self.state.registry_entries.clone(),
|
||||
cost_guard: self.state.cost_guard.clone(),
|
||||
startup_time: self.state.startup_time,
|
||||
restart_requested: std::sync::atomic::AtomicBool::new(false),
|
||||
};
|
||||
mutate(&mut new_state);
|
||||
self.state = Arc::new(new_state);
|
||||
|
||||
@@ -165,8 +165,6 @@ pub struct GatewayState {
|
||||
pub cost_guard: Option<Arc<crate::agent::cost_guard::CostGuard>>,
|
||||
/// Server startup time for uptime calculation.
|
||||
pub startup_time: std::time::Instant,
|
||||
/// Flag set when a restart has been requested via the API.
|
||||
pub restart_requested: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
/// Start the gateway HTTP server.
|
||||
@@ -247,8 +245,6 @@ pub async fn start_server(
|
||||
"/api/extensions/{name}/setup",
|
||||
get(extensions_setup_handler).post(extensions_setup_submit_handler),
|
||||
)
|
||||
// Gateway management
|
||||
.route("/api/gateway/restart", post(gateway_restart_handler))
|
||||
// Pairing
|
||||
.route("/api/pairing/{channel}", get(pairing_list_handler))
|
||||
.route(
|
||||
@@ -1218,8 +1214,8 @@ async fn extensions_list_handler(
|
||||
} else if !ext.authenticated {
|
||||
// No credentials configured yet.
|
||||
"installed".to_string()
|
||||
} else if ext.active && ext.name == "telegram" {
|
||||
// Telegram: check pairing status (end-to-end setup via web UI).
|
||||
} else if ext.active {
|
||||
// Check pairing status for active channels.
|
||||
let has_paired = pairing_store
|
||||
.read_allow_from(&ext.name)
|
||||
.map(|list| !list.is_empty())
|
||||
@@ -1230,7 +1226,7 @@ async fn extensions_list_handler(
|
||||
"pairing".to_string()
|
||||
}
|
||||
} else {
|
||||
// Authenticated but not fully active (or non-Telegram).
|
||||
// Authenticated but not yet active.
|
||||
"configured".to_string()
|
||||
})
|
||||
} else {
|
||||
@@ -1552,41 +1548,12 @@ async fn extensions_setup_submit_handler(
|
||||
Ok(result) => {
|
||||
let mut resp = ActionResponse::ok(result.message);
|
||||
resp.activated = Some(result.activated);
|
||||
if !result.activated {
|
||||
resp.needs_restart = Some(true);
|
||||
}
|
||||
Ok(Json(resp))
|
||||
}
|
||||
Err(e) => Ok(Json(ActionResponse::fail(e.to_string()))),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Gateway management handlers ---
|
||||
|
||||
async fn gateway_restart_handler(State(state): State<Arc<GatewayState>>) -> Json<ActionResponse> {
|
||||
// Idempotency guard: only allow one restart at a time.
|
||||
if state
|
||||
.restart_requested
|
||||
.compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::SeqCst,
|
||||
std::sync::atomic::Ordering::SeqCst,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Json(ActionResponse::ok("Restart already in progress"));
|
||||
}
|
||||
|
||||
// Take the shutdown sender and trigger graceful shutdown.
|
||||
if let Some(tx) = state.shutdown_tx.write().await.take() {
|
||||
let _ = tx.send(());
|
||||
tracing::info!("Gateway restart requested via API");
|
||||
}
|
||||
|
||||
Json(ActionResponse::ok("Restarting..."))
|
||||
}
|
||||
|
||||
// --- Pairing handlers ---
|
||||
|
||||
async fn pairing_list_handler(
|
||||
|
||||
@@ -1931,14 +1931,6 @@ function renderExtensionCard(ext) {
|
||||
card.appendChild(errorDiv);
|
||||
}
|
||||
|
||||
// Show "coming soon" note for non-Telegram channels that are configured but not fully supported yet
|
||||
if (ext.kind === 'wasm_channel' && ext.name !== 'telegram'
|
||||
&& (ext.activation_status === 'configured' || ext.active)) {
|
||||
const noteDiv = document.createElement('div');
|
||||
noteDiv.className = 'ext-note';
|
||||
noteDiv.textContent = 'Full integration coming soon. Use the CLI to complete setup.';
|
||||
card.appendChild(noteDiv);
|
||||
}
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'ext-actions';
|
||||
@@ -2168,10 +2160,8 @@ function submitConfigureModal(name, fields) {
|
||||
if (res.success) {
|
||||
if (res.activated) {
|
||||
showToast('Configured and activated ' + name, 'success');
|
||||
} else if (res.needs_restart) {
|
||||
showToast('Configured ' + name + '. Use Reconfigure to re-enter credentials and activate.', 'info');
|
||||
} else {
|
||||
showToast(res.message, 'success');
|
||||
showToast(res.message || 'Configuration saved but activation failed', 'warning');
|
||||
}
|
||||
} else {
|
||||
showToast(res.message || 'Configuration failed', 'error');
|
||||
@@ -2235,7 +2225,7 @@ function approvePairing(channel, code, container) {
|
||||
}).then(res => {
|
||||
if (res.success) {
|
||||
showToast('Pairing approved', 'success');
|
||||
loadPairingRequests(channel, container);
|
||||
loadExtensions();
|
||||
} else {
|
||||
showToast(res.message || 'Approve failed', 'error');
|
||||
}
|
||||
@@ -2258,53 +2248,6 @@ function stopPairingPoll() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Gateway restart ---
|
||||
|
||||
function restartGateway() {
|
||||
if (!confirm('Restart IronClaw gateway? Active connections will be dropped.')) return;
|
||||
|
||||
apiFetch('/api/gateway/restart', { method: 'POST' })
|
||||
.then(function() {
|
||||
showRestartOverlay();
|
||||
})
|
||||
.catch(function() {
|
||||
showRestartOverlay();
|
||||
});
|
||||
}
|
||||
|
||||
function showRestartOverlay() {
|
||||
var overlay = document.createElement('div');
|
||||
overlay.className = 'restart-overlay';
|
||||
overlay.innerHTML = '<div class="restart-message">'
|
||||
+ '<div class="restart-spinner"></div>'
|
||||
+ '<h2>Restarting IronClaw...</h2>'
|
||||
+ '<p>Waiting for server to come back online</p>'
|
||||
+ '</div>';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
var pollCount = 0;
|
||||
var pollTimer = setInterval(function() {
|
||||
pollCount++;
|
||||
if (pollCount > 30) { // 60 seconds
|
||||
clearInterval(pollTimer);
|
||||
overlay.querySelector('h2').textContent = 'Restart timed out';
|
||||
overlay.querySelector('p').textContent = 'Server did not come back within 60 seconds. Check logs.';
|
||||
overlay.querySelector('.restart-spinner').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
fetch('/api/gateway/status', {
|
||||
headers: { 'Authorization': 'Bearer ' + token },
|
||||
})
|
||||
.then(function(r) {
|
||||
if (r.ok) {
|
||||
clearInterval(pollTimer);
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(function() { /* still restarting */ });
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// --- WASM channel stepper ---
|
||||
|
||||
function renderWasmChannelStepper(ext) {
|
||||
@@ -2312,23 +2255,17 @@ function renderWasmChannelStepper(ext) {
|
||||
stepper.className = 'ext-stepper';
|
||||
|
||||
var status = ext.activation_status || 'installed';
|
||||
var isTelegram = ext.name === 'telegram';
|
||||
|
||||
// Telegram gets a 3-step stepper (Installed → Configured → Active/Pairing).
|
||||
// Other channels only get 2 steps (Installed → Configured) since full
|
||||
// integration isn't available in the web UI yet.
|
||||
var steps = [
|
||||
{ label: 'Installed', key: 'installed' },
|
||||
{ label: 'Configured', key: 'configured' },
|
||||
{ label: status === 'pairing' ? 'Awaiting Pairing' : 'Active', key: 'active' },
|
||||
];
|
||||
if (isTelegram) {
|
||||
steps.push({ label: status === 'pairing' ? 'Awaiting Pairing' : 'Active', key: 'active' });
|
||||
}
|
||||
|
||||
var reachedIdx;
|
||||
if (status === 'active') reachedIdx = isTelegram ? 2 : 1;
|
||||
if (status === 'active') reachedIdx = 2;
|
||||
else if (status === 'pairing') reachedIdx = 2;
|
||||
else if (status === 'failed') reachedIdx = isTelegram ? 2 : 1;
|
||||
else if (status === 'failed') reachedIdx = 2;
|
||||
else if (status === 'configured') reachedIdx = 1;
|
||||
else reachedIdx = 0;
|
||||
|
||||
|
||||
@@ -2312,43 +2312,6 @@ body {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* Restart overlay */
|
||||
.restart-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.restart-message {
|
||||
text-align: center;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.restart-message h2 {
|
||||
margin: 16px 0 8px;
|
||||
}
|
||||
|
||||
.restart-message p {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.restart-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--border);
|
||||
border-top-color: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@@ -451,9 +451,6 @@ pub struct ActionResponse {
|
||||
/// Whether the channel was successfully activated after setup.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub activated: Option<bool>,
|
||||
/// Whether a gateway restart is needed (activation failed).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub needs_restart: Option<bool>,
|
||||
}
|
||||
|
||||
impl ActionResponse {
|
||||
@@ -465,7 +462,6 @@ impl ActionResponse {
|
||||
awaiting_token: None,
|
||||
instructions: None,
|
||||
activated: None,
|
||||
needs_restart: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,7 +473,6 @@ impl ActionResponse {
|
||||
awaiting_token: None,
|
||||
instructions: None,
|
||||
activated: None,
|
||||
needs_restart: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,7 +493,6 @@ mod tests {
|
||||
registry_entries: Vec::new(),
|
||||
cost_guard: None,
|
||||
startup_time: std::time::Instant::now(),
|
||||
restart_requested: std::sync::atomic::AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user