From 765e433149ba787260ffcb50f1acc8a8314fc292 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 9 Nov 2026 08:25:25 +0000 Subject: [PATCH 1/2] feat(gateway): songs and images survive the caller restarting; GET /v1/result/{id}; v0.2.90 With an x-request-id, /v1/music and /v1/images now run the job in a detached task that holds its queue slot to the end: a dropped connection (infernetprotocol.com redeploys cut every song in flight three times in 20 minutes on 2026-11-09) no longer cancels the job. The finished result is kept 30 min (bounded, 48 entries) and GET /v1/result/{id} returns it (200), its live progress while it runs (202) or 404. Co-Authored-By: Claude Opus 5.5 --- node/crates/c0mpute-core/src/job_progress.rs | 67 +++++++ .../crates/c0mpute-core/src/speech_gateway.rs | 171 +++++++++++++----- 2 files changed, 194 insertions(+), 44 deletions(-) diff --git a/node/crates/c0mpute-core/src/job_progress.rs b/node/crates/c0mpute-core/src/job_progress.rs index 0b34660..f63099e 100644 --- a/node/crates/c0mpute-core/src/job_progress.rs +++ b/node/crates/c0mpute-core/src/job_progress.rs @@ -91,6 +91,51 @@ impl ProgressBoard { } } +/// A finished tracked request, kept so a caller that lost its connection (an +/// app restarting mid-song) can still collect it: `GET /v1/result/{id}`. +#[derive(Clone, Debug)] +pub enum Stored { + Done { + bytes: Arc>, + content_type: &'static str, + job_id: String, + worker: String, + }, + Failed { + status: u16, + message: String, + }, +} + +/// How many finished results are held at once (songs are ~2-5 MB each). +const MAX_RESULTS: usize = 48; + +#[derive(Clone, Default)] +pub struct ResultStore(Arc>>); + +impl ResultStore { + pub fn put(&self, key: &str, stored: Stored) { + let now = now_ms(); + let Ok(mut map) = self.0.lock() else { return }; + map.retain(|_, (at, _)| now.saturating_sub(*at) < KEEP_MS); + while map.len() >= MAX_RESULTS { + let Some(oldest) = map.iter().min_by_key(|(_, (at, _))| *at).map(|(k, _)| k.clone()) else { + break; + }; + map.remove(&oldest); + } + map.insert(key.to_string(), (now, stored)); + } + + pub fn get(&self, key: &str) -> Option { + let now = now_ms(); + let map = self.0.lock().ok()?; + map.get(key) + .filter(|(at, _)| now.saturating_sub(*at) < KEEP_MS) + .map(|(_, s)| s.clone()) + } +} + /// A caller-chosen request id: short and URL-safe. pub fn valid_key(key: &str) -> bool { !key.is_empty() @@ -145,6 +190,28 @@ mod tests { assert!(board.snapshot("nope").is_none()); } + #[test] + fn finished_results_are_kept_for_a_caller_that_reconnects() { + let store = ResultStore::default(); + assert!(store.get("song-1").is_none()); + store.put( + "song-1", + Stored::Done { + bytes: Arc::new(vec![1, 2, 3]), + content_type: "audio/mpeg", + job_id: "j".into(), + worker: "w".into(), + }, + ); + store.put("song-2", Stored::Failed { status: 503, message: "no worker".into() }); + assert!(matches!(store.get("song-1"), Some(Stored::Done { ref bytes, .. }) if bytes.as_slice() == [1, 2, 3])); + assert!(matches!(store.get("song-2"), Some(Stored::Failed { status: 503, .. }))); + for i in 0..(MAX_RESULTS + 5) { + store.put(&format!("k{i}"), Stored::Failed { status: 502, message: String::new() }); + } + assert!(store.0.lock().unwrap().len() <= MAX_RESULTS, "bounded"); + } + #[test] fn request_ids_must_be_short_and_url_safe() { assert!(valid_key("17a1b21d-a95e-4345-a3fe-d14dfd6868e3")); diff --git a/node/crates/c0mpute-core/src/speech_gateway.rs b/node/crates/c0mpute-core/src/speech_gateway.rs index 5acb0b0..5fc92d7 100644 --- a/node/crates/c0mpute-core/src/speech_gateway.rs +++ b/node/crates/c0mpute-core/src/speech_gateway.rs @@ -62,6 +62,8 @@ struct GatewayState { penalties: crate::penalty_box::PenaltyBox, /// Live stages of long requests (songs), by the caller's x-request-id. progress: crate::job_progress::ProgressBoard, + /// Finished tracked requests, collectable at GET /v1/result/{id}. + results: crate::job_progress::ResultStore, } #[derive(Debug)] @@ -179,6 +181,7 @@ pub fn router( embed_slots: Arc::new(Semaphore::new(MAX_CONCURRENT_EMBED_JOBS)), sessions: crate::speech_session::SessionStore::new(), progress: crate::job_progress::ProgressBoard::default(), + results: crate::job_progress::ResultStore::default(), penalties, }; Router::new() @@ -189,6 +192,7 @@ pub fn router( .route("/v1/images", post(generate_image)) .route("/v1/music", post(generate_music)) .route("/v1/progress/{request}", get(job_progress)) + .route("/v1/result/{request}", get(job_result)) .route("/v1/videos", post(generate_video)) // OCR takes a 32 MiB document, above the router-wide audio limit. .route( @@ -1048,7 +1052,7 @@ async fn generate_music( input .validate() .map_err(|message| ApiError(StatusCode::BAD_REQUEST, message))?; - let _slot = state + let slot = state .music_slots .clone() .acquire_owned() @@ -1056,13 +1060,10 @@ async fn generate_music( .map_err(|_| ApiError(StatusCode::SERVICE_UNAVAILABLE, "music queue is unavailable".into()))?; let spec = serde_json::to_value(input.clip_for_model()) .map_err(|error| gateway_failure(error.into()))?; - let request_id = headers - .get("x-request-id") - .and_then(|v| v.to_str().ok()) - .filter(|k| crate::job_progress::valid_key(k)) - .map(str::to_string); - let job = run_speech_job( + let done = run_tracked( &state, + request_id(&headers), + slot, crate::music::WORKLOAD_TXT2MUSIC, spec, vec![ @@ -1070,35 +1071,126 @@ async fn generate_music( crate::music::MODEL_TAG.to_string(), ], crate::music::MAX_RESULT_BYTES, - ); - let result = match &request_id { - Some(key) => { - state.progress.start(key); - crate::job_progress::CURRENT - .scope((state.progress.clone(), key.clone()), async { - let result = job.await; - crate::job_progress::set_stage(if result.is_ok() { "done" } else { "failed" }); - result - }) - .await - } - None => job.await, + |bytes| crate::music::audio_content_type(bytes).unwrap_or("audio/mpeg"), + ) + .await?; + Ok(stored_response(done)) +} + +/// The caller's `x-request-id`, when it is short and URL-safe. +fn request_id(headers: &HeaderMap) -> Option { + headers + .get("x-request-id") + .and_then(|v| v.to_str().ok()) + .filter(|k| crate::job_progress::valid_key(k)) + .map(str::to_string) +} + +/// Run a long job (song, image). With a request id it runs DETACHED from the +/// HTTP request: if the caller's connection drops (Infernet redeploying +/// mid-song cut every song in flight, 2026-11-09), the job carries on, holds +/// its queue slot to the end, and its result waits at GET /v1/result/{id}. +/// Progress is recorded under the same id (see `job_progress`). +#[allow(clippy::too_many_arguments)] +async fn run_tracked( + state: &GatewayState, + request_id: Option, + slot: tokio::sync::OwnedSemaphorePermit, + workload: &'static str, + spec: serde_json::Value, + required: Vec, + max_bytes: usize, + content_type: fn(&[u8]) -> &'static str, +) -> ApiResult { + use crate::job_progress::Stored; + let Some(key) = request_id else { + let (outcome, bytes) = run_speech_job(state, workload, spec, required, max_bytes).await?; + drop(slot); + return Ok(Stored::Done { + content_type: content_type(&bytes), + bytes: Arc::new(bytes), + job_id: outcome.job_id, + worker: outcome.accepted_bid.bidder_peer_id, + }); }; - let (outcome, bytes) = result?; - let content_type = crate::music::audio_content_type(&bytes).unwrap_or("audio/mpeg"); - let mut response = (StatusCode::OK, bytes).into_response(); + state.progress.start(&key); + let st = state.clone(); + let task_key = key.clone(); + let task = tokio::spawn(crate::job_progress::CURRENT.scope( + (state.progress.clone(), key.clone()), + async move { + let _slot = slot; + let result = run_speech_job(&st, workload, spec, required, max_bytes).await; + crate::job_progress::set_stage(if result.is_ok() { "done" } else { "failed" }); + let stored = match result { + Ok((outcome, bytes)) => Stored::Done { + content_type: content_type(&bytes), + bytes: Arc::new(bytes), + job_id: outcome.job_id, + worker: outcome.accepted_bid.bidder_peer_id, + }, + Err(ApiError(status, message)) => Stored::Failed { status: status.as_u16(), message }, + }; + st.results.put(&task_key, stored.clone()); + stored + }, + )); + match task.await { + Ok(Stored::Failed { status, message }) => Err(ApiError( + StatusCode::from_u16(status).unwrap_or(StatusCode::BAD_GATEWAY), + message, + )), + Ok(done) => Ok(done), + Err(error) => Err(ApiError(StatusCode::BAD_GATEWAY, format!("job task failed: {error}"))), + } +} + +/// 200 with the bytes, their type and the job headers. +fn stored_response(done: crate::job_progress::Stored) -> Response { + let crate::job_progress::Stored::Done { bytes, content_type, job_id, worker } = done else { + return (StatusCode::BAD_GATEWAY, "job failed").into_response(); + }; + let mut response = (StatusCode::OK, (*bytes).clone()).into_response(); response .headers_mut() .insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type)); response .headers_mut() .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); - set_job_headers( - response.headers_mut(), - &outcome.job_id, - &outcome.accepted_bid.bidder_peer_id, - ); - Ok(response) + set_job_headers(response.headers_mut(), &job_id, &worker); + response +} + +/// `GET /v1/result/{request}`: a tracked request's result (200 with the +/// bytes), its live progress while it runs (202 JSON), or 404. Lets a caller +/// that restarted mid-job collect it. +async fn job_result( + State(state): State, + headers: HeaderMap, + Path(request): Path, +) -> ApiResult { + authenticate(&state, &headers)?; + if !crate::job_progress::valid_key(&request) { + return Err(ApiError(StatusCode::BAD_REQUEST, "bad request id".into())); + } + match state.results.get(&request) { + Some(crate::job_progress::Stored::Failed { status, message }) => Err(ApiError( + StatusCode::from_u16(status).unwrap_or(StatusCode::BAD_GATEWAY), + message, + )), + Some(done) => Ok(stored_response(done)), + None => { + let snapshot = state + .progress + .snapshot(&request) + .ok_or_else(|| ApiError(StatusCode::NOT_FOUND, "no such request".into()))?; + let mut response = (StatusCode::ACCEPTED, Json(snapshot)).into_response(); + response + .headers_mut() + .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); + Ok(response) + } + } } /// `GET /v1/progress/{request}`: the live stage of a request sent with that @@ -1132,7 +1224,7 @@ async fn generate_image( input .validate() .map_err(|message| ApiError(StatusCode::BAD_REQUEST, message))?; - let _slot = state + let slot = state .image_slots .clone() .acquire_owned() @@ -1144,8 +1236,10 @@ async fn generate_image( ) })?; let spec = serde_json::to_value(&input).map_err(|error| gateway_failure(error.into()))?; - let (outcome, bytes) = run_speech_job( + let done = run_tracked( &state, + request_id(&headers), + slot, crate::diffusion::WORKLOAD_TXT2IMG, spec, vec![ @@ -1153,21 +1247,10 @@ async fn generate_image( crate::diffusion::MODEL_TAG.to_string(), ], crate::diffusion::MAX_IMAGE_BYTES, + |_| "image/png", ) .await?; - let mut response = (StatusCode::OK, bytes).into_response(); - response - .headers_mut() - .insert(header::CONTENT_TYPE, HeaderValue::from_static("image/png")); - response - .headers_mut() - .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); - set_job_headers( - response.headers_mut(), - &outcome.job_id, - &outcome.accepted_bid.bidder_peer_id, - ); - Ok(response) + Ok(stored_response(done)) } #[derive(Debug, Deserialize)] From 12a2c3a1c571f9afd12c1a3f7a4fb03bc0d1845d Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 9 Nov 2026 08:34:40 +0000 Subject: [PATCH 2/2] chore: bump v0.2.91 (v0.2.90 is #185's) Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 32 +++++++++---------- Cargo.toml | 2 +- apps/tui/package.json | 2 +- apps/web/package.json | 2 +- .../web/src/app/releases/latest.json/route.ts | 2 +- package.json | 2 +- packages/shared/package.json | 2 +- plugins/ad-generator-agent/module.toml | 2 +- plugins/ads-manager/module.toml | 2 +- plugins/coinpay/module.toml | 2 +- plugins/crawl/module.toml | 2 +- plugins/crowdgpt/module.toml | 2 +- plugins/ctv-ads/module.toml | 2 +- plugins/diffusion/module.toml | 2 +- plugins/embed/module.toml | 2 +- plugins/gameserver/module.toml | 2 +- plugins/hosting/module.toml | 2 +- plugins/infernet/module.toml | 2 +- plugins/live-stream/module.toml | 2 +- plugins/music/module.toml | 2 +- plugins/ocr/module.toml | 2 +- plugins/podcast-ads/module.toml | 2 +- plugins/podcasting/module.toml | 2 +- plugins/podcasts/module.toml | 2 +- plugins/render/module.toml | 2 +- plugins/secure-chat/module.toml | 2 +- plugins/storage/module.toml | 2 +- plugins/train/module.toml | 2 +- plugins/transcode/module.toml | 2 +- plugins/tts/module.toml | 2 +- plugins/video/module.toml | 2 +- plugins/webhook/module.toml | 2 +- plugins/whisper/module.toml | 2 +- plugins/workflow-step/module.toml | 2 +- 34 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4e3a7a..8545cbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,7 +478,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "c0mpute-api" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "c0mpute-proto", @@ -493,7 +493,7 @@ dependencies = [ [[package]] name = "c0mpute-bench" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "blake3", @@ -504,7 +504,7 @@ dependencies = [ [[package]] name = "c0mpute-cli" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "axum", @@ -534,7 +534,7 @@ dependencies = [ [[package]] name = "c0mpute-core" -version = "0.2.90" +version = "0.2.91" dependencies = [ "aes-gcm", "anyhow", @@ -580,7 +580,7 @@ dependencies = [ [[package]] name = "c0mpute-doctor" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "serde", @@ -592,7 +592,7 @@ dependencies = [ [[package]] name = "c0mpute-envelope" -version = "0.2.90" +version = "0.2.91" dependencies = [ "blake3", "bs58", @@ -605,7 +605,7 @@ dependencies = [ [[package]] name = "c0mpute-gateway" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "axum", @@ -627,7 +627,7 @@ dependencies = [ [[package]] name = "c0mpute-market" -version = "0.2.90" +version = "0.2.91" dependencies = [ "c0mpute-envelope", "serde", @@ -637,7 +637,7 @@ dependencies = [ [[package]] name = "c0mpute-net" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "async-trait", @@ -654,7 +654,7 @@ dependencies = [ [[package]] name = "c0mpute-placement" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "async-trait", @@ -675,7 +675,7 @@ dependencies = [ [[package]] name = "c0mpute-proto" -version = "0.2.90" +version = "0.2.91" dependencies = [ "blake3", "hex", @@ -687,7 +687,7 @@ dependencies = [ [[package]] name = "c0mpute-secure-chat" -version = "0.2.90" +version = "0.2.91" dependencies = [ "aes-gcm", "anyhow", @@ -710,7 +710,7 @@ dependencies = [ [[package]] name = "c0mpute-store" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "blake3", @@ -728,7 +728,7 @@ dependencies = [ [[package]] name = "c0mpute-transcode" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "c0mpute-proto", @@ -741,7 +741,7 @@ dependencies = [ [[package]] name = "c0mpute-update" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "hex", @@ -755,7 +755,7 @@ dependencies = [ [[package]] name = "c0mpute-verify" -version = "0.2.90" +version = "0.2.91" dependencies = [ "anyhow", "blake3", diff --git a/Cargo.toml b/Cargo.toml index e8534be..1a388ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.2.90" +version = "0.2.91" edition = "2024" license = "MIT" repository = "https://github.com/profullstack/c0mpute" diff --git a/apps/tui/package.json b/apps/tui/package.json index 30b83c7..392c8d3 100644 --- a/apps/tui/package.json +++ b/apps/tui/package.json @@ -1,6 +1,6 @@ { "name": "@c0mpute/tui", - "version": "0.2.90", + "version": "0.2.91", "private": true, "type": "module", "bin": { diff --git a/apps/web/package.json b/apps/web/package.json index f6a95dc..3671084 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "c0mpute", - "version": "0.2.90", + "version": "0.2.91", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/app/releases/latest.json/route.ts b/apps/web/src/app/releases/latest.json/route.ts index 9f355a4..7974c66 100644 --- a/apps/web/src/app/releases/latest.json/route.ts +++ b/apps/web/src/app/releases/latest.json/route.ts @@ -28,7 +28,7 @@ export const dynamic = "force-dynamic"; * this in sync with the workspace version in `Cargo.toml` on each release so a * freshly built CLI reports "already latest" rather than a phantom upgrade. */ -const FALLBACK_VERSION = "0.2.90"; +const FALLBACK_VERSION = "0.2.91"; const FALLBACK_MIN_REQUIRED = "0.0.1"; type Channel = "stable" | "beta" | "nightly"; diff --git a/package.json b/package.json index 3ca2097..b948eb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "c0mpute-monorepo", - "version": "0.2.90", + "version": "0.2.91", "private": true, "workspaces": [ "apps/*", diff --git a/packages/shared/package.json b/packages/shared/package.json index 79a3d96..8b959c0 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@c0mpute/shared", - "version": "0.2.90", + "version": "0.2.91", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/plugins/ad-generator-agent/module.toml b/plugins/ad-generator-agent/module.toml index 19c23e1..386d920 100644 --- a/plugins/ad-generator-agent/module.toml +++ b/plugins/ad-generator-agent/module.toml @@ -21,7 +21,7 @@ [module] id = "ad-generator-agent" name = "Ad Generator Agent — every ad type from one URL" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "applications" status = "available" diff --git a/plugins/ads-manager/module.toml b/plugins/ads-manager/module.toml index 5ea6b05..0c157a5 100644 --- a/plugins/ads-manager/module.toml +++ b/plugins/ads-manager/module.toml @@ -38,7 +38,7 @@ [module] id = "ads-manager" name = "Ads Manager — shared ad operations layer" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "applications" status = "partial" diff --git a/plugins/coinpay/module.toml b/plugins/coinpay/module.toml index 8d98a76..636711a 100644 --- a/plugins/coinpay/module.toml +++ b/plugins/coinpay/module.toml @@ -7,7 +7,7 @@ [module] id = "coinpay" name = "CoinPay — DID, payments, escrow, reputation" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "applications" status = "available" diff --git a/plugins/crawl/module.toml b/plugins/crawl/module.toml index da8ffdc..3d68522 100644 --- a/plugins/crawl/module.toml +++ b/plugins/crawl/module.toml @@ -8,7 +8,7 @@ [module] id = "crawl" name = "Crawl — distributed web fetching" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "data" status = "available" diff --git a/plugins/crowdgpt/module.toml b/plugins/crowdgpt/module.toml index ddd87b8..ea5b963 100644 --- a/plugins/crowdgpt/module.toml +++ b/plugins/crowdgpt/module.toml @@ -43,7 +43,7 @@ [module] id = "crowdgpt" name = "CrowdGPT: donate idle compute" -version = "0.2.90" +version = "0.2.91" kind = "donation" category = "ai" status = "available" diff --git a/plugins/ctv-ads/module.toml b/plugins/ctv-ads/module.toml index cf8bfc2..48250c5 100644 --- a/plugins/ctv-ads/module.toml +++ b/plugins/ctv-ads/module.toml @@ -28,7 +28,7 @@ [module] id = "ctv-ads" name = "CTV Ads — VAST and VMAP from every gateway" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "applications" status = "partial" diff --git a/plugins/diffusion/module.toml b/plugins/diffusion/module.toml index ed7eddd..39a0408 100644 --- a/plugins/diffusion/module.toml +++ b/plugins/diffusion/module.toml @@ -11,7 +11,7 @@ [module] id = "diffusion" name = "Diffusion — image generation" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "ai" status = "available" diff --git a/plugins/embed/module.toml b/plugins/embed/module.toml index a6b719d..da7315c 100644 --- a/plugins/embed/module.toml +++ b/plugins/embed/module.toml @@ -19,7 +19,7 @@ [module] id = "embed" name = "Embeddings — vector generation" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "ai" status = "available" diff --git a/plugins/gameserver/module.toml b/plugins/gameserver/module.toml index da84ca1..8eecf87 100644 --- a/plugins/gameserver/module.toml +++ b/plugins/gameserver/module.toml @@ -55,7 +55,7 @@ [module] id = "gameserver" name = "Game server — Minecraft / Factorio / Valheim" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "infrastructure" status = "available" diff --git a/plugins/hosting/module.toml b/plugins/hosting/module.toml index a8d870c..9f10ac2 100644 --- a/plugins/hosting/module.toml +++ b/plugins/hosting/module.toml @@ -30,7 +30,7 @@ [module] id = "hosting" name = "Hosting — p2p static sites" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "data" status = "available" diff --git a/plugins/infernet/module.toml b/plugins/infernet/module.toml index e185a6f..5748e0d 100644 --- a/plugins/infernet/module.toml +++ b/plugins/infernet/module.toml @@ -22,7 +22,7 @@ [module] id = "infernet" name = "Infernet Protocol — AI inference" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "ai" status = "available" diff --git a/plugins/live-stream/module.toml b/plugins/live-stream/module.toml index c9edcd3..673bc2c 100644 --- a/plugins/live-stream/module.toml +++ b/plugins/live-stream/module.toml @@ -34,7 +34,7 @@ [module] id = "live-stream" name = "Live Stream — p2p HLS live streaming" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "media" status = "available" diff --git a/plugins/music/module.toml b/plugins/music/module.toml index c00e85a..41b8ed4 100644 --- a/plugins/music/module.toml +++ b/plugins/music/module.toml @@ -15,7 +15,7 @@ [module] id = "music" name = "Music — song generation (ACE-Step 1.5)" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "ai" status = "available" diff --git a/plugins/ocr/module.toml b/plugins/ocr/module.toml index 37ed778..57696f6 100644 --- a/plugins/ocr/module.toml +++ b/plugins/ocr/module.toml @@ -19,7 +19,7 @@ [module] id = "ocr" name = "OCR — document text extraction" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "ai" status = "available" diff --git a/plugins/podcast-ads/module.toml b/plugins/podcast-ads/module.toml index b2770cc..b30e952 100644 --- a/plugins/podcast-ads/module.toml +++ b/plugins/podcast-ads/module.toml @@ -31,7 +31,7 @@ [module] id = "podcast-ads" name = "Podcast Ads — dynamic ad insertion" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "applications" status = "partial" diff --git a/plugins/podcasting/module.toml b/plugins/podcasting/module.toml index 823d563..0386297 100644 --- a/plugins/podcasting/module.toml +++ b/plugins/podcasting/module.toml @@ -24,7 +24,7 @@ [module] id = "podcasting" name = "Podcasting — p2p podcast publishing" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "media" status = "available" diff --git a/plugins/podcasts/module.toml b/plugins/podcasts/module.toml index d998b46..d1b8dc1 100644 --- a/plugins/podcasts/module.toml +++ b/plugins/podcasts/module.toml @@ -24,7 +24,7 @@ [module] id = "podcasts" name = "Podcasts — p2p podcast listener" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "media" status = "available" diff --git a/plugins/render/module.toml b/plugins/render/module.toml index 77cd90b..db18193 100644 --- a/plugins/render/module.toml +++ b/plugins/render/module.toml @@ -33,7 +33,7 @@ [module] id = "render" name = "Render — Blender 3D rendering" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "media" status = "available" diff --git a/plugins/secure-chat/module.toml b/plugins/secure-chat/module.toml index 2b1fe95..ccc5c20 100644 --- a/plugins/secure-chat/module.toml +++ b/plugins/secure-chat/module.toml @@ -21,7 +21,7 @@ [module] id = "secure-chat" name = "Secure Chat — E2E encrypted p2p messaging" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "applications" status = "available" diff --git a/plugins/storage/module.toml b/plugins/storage/module.toml index 0c06bf2..3966697 100644 --- a/plugins/storage/module.toml +++ b/plugins/storage/module.toml @@ -20,7 +20,7 @@ [module] id = "storage" name = "Storage — Reed-Solomon shard service" -version = "0.2.90" +version = "0.2.91" kind = "service" category = "data" status = "available" diff --git a/plugins/train/module.toml b/plugins/train/module.toml index 5117347..b7bb856 100644 --- a/plugins/train/module.toml +++ b/plugins/train/module.toml @@ -55,7 +55,7 @@ [module] id = "train" name = "Fine-tune — LoRA training" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "ai" status = "available" diff --git a/plugins/transcode/module.toml b/plugins/transcode/module.toml index 596f1b5..8182ca9 100644 --- a/plugins/transcode/module.toml +++ b/plugins/transcode/module.toml @@ -7,7 +7,7 @@ [module] id = "transcode" name = "FFmpeg Transcoding" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "media" status = "available" diff --git a/plugins/tts/module.toml b/plugins/tts/module.toml index 85e8bf9..cd461f3 100644 --- a/plugins/tts/module.toml +++ b/plugins/tts/module.toml @@ -15,7 +15,7 @@ [module] id = "tts" name = "TTS — text-to-speech" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "ai" status = "available" diff --git a/plugins/video/module.toml b/plugins/video/module.toml index 08a76ee..24ad670 100644 --- a/plugins/video/module.toml +++ b/plugins/video/module.toml @@ -65,7 +65,7 @@ [module] id = "video" name = "Video — generation (Wan 2.2, Wan 2.1 1.3B on 8 GiB or CPU, LTX-2.3 with audio)" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "ai" status = "available" diff --git a/plugins/webhook/module.toml b/plugins/webhook/module.toml index c9f8eec..b37d363 100644 --- a/plugins/webhook/module.toml +++ b/plugins/webhook/module.toml @@ -7,7 +7,7 @@ [module] id = "webhook" name = "Webhooks — paid, end-to-end encrypted delivery" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "infrastructure" status = "available" diff --git a/plugins/whisper/module.toml b/plugins/whisper/module.toml index e8bc326..7cabad2 100644 --- a/plugins/whisper/module.toml +++ b/plugins/whisper/module.toml @@ -7,7 +7,7 @@ [module] id = "whisper" name = "Whisper — speech-to-text" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "ai" status = "available" diff --git a/plugins/workflow-step/module.toml b/plugins/workflow-step/module.toml index 3ed6fae..2cef36f 100644 --- a/plugins/workflow-step/module.toml +++ b/plugins/workflow-step/module.toml @@ -14,7 +14,7 @@ [module] id = "workflow-step" name = "Workflow step — webhook workflow steps (meshhook)" -version = "0.2.90" +version = "0.2.91" kind = "workload" category = "infrastructure" status = "available"