Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a340829
uis: reduce opacity
nick1udwig Nov 3, 2025
75d0b40
Format Rust code using rustfmt
github-actions[bot] Nov 3, 2025
9285d13
bump version to 1.8.1
nick1udwig Nov 5, 2025
64ca272
contacts: change hero image to 20% opacity
nick1udwig Nov 8, 2025
5ce675f
register-ui: make it harder to reset other node
nick1udwig Nov 14, 2025
50ac5a7
Format Rust code using rustfmt
github-actions[bot] Nov 14, 2025
8c3c54f
Merge pull request #888 from hyperware-ai/hf/register-ui-make-harder-…
nick1udwig Nov 14, 2025
7ecc376
app-store: robustify manual download
nick1udwig Nov 17, 2025
99d1ba5
Merge branch 'develop' into hf/ui-reduce-opacity
nick1udwig Nov 17, 2025
49913d3
Merge pull request #883 from hyperware-ai/hf/ui-reduce-opacity
nick1udwig Nov 17, 2025
90a9b43
Merge pull request #889 from hyperware-ai/hf/app-store-robustify-manu…
nick1udwig Nov 17, 2025
118b61f
add public mode
nick1udwig Nov 20, 2025
eaa9e53
spider: add todo as self-validating app
nick1udwig Nov 21, 2025
f54aee1
spider: update default anthropic model and update hyperware prompts
nick1udwig Nov 25, 2025
d31436d
Format Rust code using rustfmt
github-actions[bot] Nov 25, 2025
205d95b
Merge pull request #891 from hyperware-ai/hf/spider-add-todo-self-val…
nick1udwig Nov 25, 2025
52b3a9b
Merge branch 'develop' into hf/add-public-mode
nick1udwig Nov 25, 2025
45afac2
Merge pull request #890 from hyperware-ai/hf/add-public-mode
nick1udwig Nov 25, 2025
8158101
spider: increase robustness
nick1udwig Nov 26, 2025
125426e
spider: replace call_api method+args with signature
nick1udwig Nov 26, 2025
84781b5
Format Rust code using rustfmt
github-actions[bot] Nov 26, 2025
dfbbed0
Merge pull request #892 from hyperware-ai/hf/spider-fix-bugs
nick1udwig Nov 26, 2025
57220dc
Merge pull request #886 from hyperware-ai/develop
nick1udwig Nov 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "hyperdrive_lib"
authors = ["Sybil Technologies AG"]
version = "1.8.0"
version = "1.8.1"
edition = "2021"
description = "A general-purpose sovereign cloud computing platform"
homepage = "https://hyperware.ai"
Expand Down
3 changes: 2 additions & 1 deletion hyperdrive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "hyperdrive"
authors = ["Sybil Technologies AG"]
version = "1.8.0"
version = "1.8.1"
edition = "2021"
description = "A general-purpose sovereign cloud computing platform"
homepage = "https://hyperware.ai"
Expand All @@ -17,6 +17,7 @@ anyhow = "1.0.71"
sha2 = "0.10.8"

[features]
public-mode = []
simulation-mode = []

[dependencies]
Expand Down
234 changes: 216 additions & 18 deletions hyperdrive/packages/app-store/downloads/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@
//! Note: While this process coordinates file transfers, the actual chunked transfer
//! mechanism is implemented in the FT worker for improved modularity and performance.
//!
use crate::hyperware::process::downloads::{
AutoDownloadCompleteRequest, AutoDownloadError, AutoUpdateRequest, DirEntry,
DownloadCompleteRequest, DownloadError, DownloadRequest, DownloadResponse, Entry, FileEntry,
HashMismatch, LocalDownloadRequest, RemoteDownloadRequest, RemoveFileRequest,
use crate::hyperware::process::{
chain::{ChainRequest, ChainResponse},
downloads::{
AutoDownloadCompleteRequest, AutoDownloadError, AutoUpdateRequest, DirEntry,
DownloadCompleteRequest, DownloadError, DownloadRequest, DownloadResponse, Entry,
FileEntry, HashMismatch, LocalDownloadRequest, RemoteDownloadRequest, RemoveFileRequest,
},
};
use ft_worker_lib::{spawn_receive_transfer, spawn_send_transfer};
use hyperware::process::downloads::AutoDownloadSuccess;
Expand Down Expand Up @@ -72,6 +75,7 @@ wit_bindgen::generate!({
mod ft_worker_lib;

pub const VFS_TIMEOUT: u64 = 5; // 5s
pub const CHAIN_TIMEOUT: u64 = 60; // 60s

#[derive(Debug, Serialize, Deserialize, process_macros::SerdeJsonInto)]
#[serde(untagged)] // untagged as a meta-type for all incoming responses
Expand All @@ -89,6 +93,15 @@ pub struct AutoUpdateStatus {

type AutoUpdates = HashMap<(PackageId, String), AutoUpdateStatus>;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ManualDownloadStatus {
mirrors_left: Vec<String>, // vec(node/url)
mirrors_failed: Vec<(String, DownloadError)>, // vec(node/url, error)
active_mirror: String, // (node/url)
}

type ManualDownloads = HashMap<(PackageId, String), ManualDownloadStatus>;

#[derive(Debug, Serialize, Deserialize)]
pub struct State {
// persisted metadata about which packages we are mirroring
Expand Down Expand Up @@ -128,6 +141,8 @@ fn init(our: Address) {

// metadata for in-flight auto-updates
let mut auto_updates: AutoUpdates = HashMap::new();
// metadata for in-flight manual downloads (used for mirror retries)
let mut manual_downloads: ManualDownloads = HashMap::new();

loop {
match await_message() {
Expand All @@ -139,6 +154,7 @@ fn init(our: Address) {
&mut downloads,
// &mut tmp,
&mut auto_updates,
&mut manual_downloads,
) {
print_to_terminal(1, &format!("error handling message: {e:?}"));
}
Expand All @@ -163,6 +179,17 @@ fn init(our: Address) {
// Then remove and get metadata
if let Some(metadata) = auto_updates.remove(&key) {
try_next_mirror(metadata, key, &mut auto_updates, error);
} else if let Some(metadata) = manual_downloads.remove(&key) {
if let Err(err) =
try_next_manual_mirror(metadata, key, &mut manual_downloads, error)
{
print_to_terminal(
1,
&format!(
"downloads: failed manual mirror retry on send error: {err:?}"
),
);
}
}
}
}
Expand All @@ -182,6 +209,7 @@ fn handle_message(
downloads: &mut Directory,
// _tmp: &mut Directory,
auto_updates: &mut AutoUpdates,
manual_downloads: &mut ManualDownloads,
) -> anyhow::Result<()> {
if message.is_request() {
match message.body().try_into()? {
Expand All @@ -208,6 +236,18 @@ fn handle_message(
desired_version_hash,
} = download_request.clone();

let key = (
package_id.clone().to_process_lib(),
desired_version_hash.clone(),
);

if !download_from.starts_with("http")
&& !auto_updates.contains_key(&key)
&& !manual_downloads.contains_key(&key)
{
build_manual_mirror_status(&download_request, manual_downloads)?;
}

if download_from.starts_with("http") {
// use http-client to GET it
Request::to(("our", "http-client", "distro", "sys"))
Expand Down Expand Up @@ -310,6 +350,16 @@ fn handle_message(
DownloadError::InvalidManifest,
);
}
return Ok(());
}

if let Some(err) = req.err {
if let Some(metadata) = manual_downloads.remove(&key) {
try_next_manual_mirror(metadata, key, manual_downloads, err)?;
return Ok(());
}
} else {
manual_downloads.remove(&key);
}
}
DownloadRequest::GetFiles(maybe_id) => {
Expand Down Expand Up @@ -534,26 +584,31 @@ fn handle_message(

if let Some(context) = message.context() {
let download_request = serde_json::from_slice::<LocalDownloadRequest>(context)?;
let key = (
download_request.package_id.clone().to_process_lib(),
download_request.desired_version_hash.clone(),
);
match download_response {
DownloadResponse::Err(e) => {
print_to_terminal(1, &format!("downloads: got error response: {e:?}"));
let key = (
download_request.package_id.clone().to_process_lib(),
download_request.desired_version_hash.clone(),
);

if let Some(metadata) = auto_updates.remove(&key) {
try_next_mirror(metadata, key, auto_updates, e);
} else {
// If not an auto-update, forward error normally
Request::to(("our", "main", "app-store", "sys"))
.body(DownloadCompleteRequest {
package_id: download_request.package_id,
version_hash: download_request.desired_version_hash,
err: Some(e),
})
.send()?;
return Ok(());
}

if let Some(metadata) = manual_downloads.remove(&key) {
try_next_manual_mirror(metadata, key, manual_downloads, e)?;
return Ok(());
}

// If not handled by retry logic, forward error normally
Request::to(("our", "main", "app-store", "sys"))
.body(DownloadCompleteRequest {
package_id: download_request.package_id,
version_hash: download_request.desired_version_hash,
err: Some(e),
})
.send()?;
}
DownloadResponse::Success => {
// todo: maybe we do something here.
Expand Down Expand Up @@ -646,6 +701,149 @@ fn handle_message(
Ok(())
}

fn build_manual_mirror_status(
download_request: &LocalDownloadRequest,
manual_downloads: &mut ManualDownloads,
) -> anyhow::Result<()> {
let key = (
download_request.package_id.clone().to_process_lib(),
download_request.desired_version_hash.clone(),
);

if manual_downloads.contains_key(&key) {
return Ok(());
}

let mut mirror_candidates = match fetch_mirror_candidates(&key.0) {
Ok(candidates) => candidates,
Err(err) => {
print_to_terminal(
1,
&format!("downloads: failed to fetch mirrors for manual download: {err:?}"),
);
Vec::new()
}
};

// ensure requested mirror is first
match mirror_candidates
.iter()
.position(|m| m == &download_request.download_from)
{
Some(idx) => {
let requested = mirror_candidates.remove(idx);
mirror_candidates.insert(0, requested);
}
None => mirror_candidates.insert(0, download_request.download_from.clone()),
}

if mirror_candidates.len() <= 1 {
return Ok(());
}

manual_downloads.insert(
key,
ManualDownloadStatus {
mirrors_left: mirror_candidates[1..].to_vec(),
mirrors_failed: Vec::new(),
active_mirror: mirror_candidates[0].clone(),
},
);

Ok(())
}

fn fetch_mirror_candidates(package_id: &PackageId) -> anyhow::Result<Vec<String>> {
let resp = Request::to(("our", "chain", "app-store", "sys"))
.body(serde_json::to_vec(&ChainRequest::GetApp(
crate::hyperware::process::main::PackageId::from_process_lib(package_id.clone()),
))?)
.send_and_await_response(CHAIN_TIMEOUT)??;

let msg = serde_json::from_slice::<ChainResponse>(resp.body())?;

if let ChainResponse::GetApp(Some(app)) = msg {
if let Some(metadata) = app.metadata {
let mut seen = HashSet::new();
let mut mirror_candidates: Vec<String> = Vec::new();

if !metadata.properties.publisher.is_empty()
&& seen.insert(metadata.properties.publisher.clone())
{
mirror_candidates.push(metadata.properties.publisher);
}

for mirror in metadata.properties.mirrors {
if mirror.is_empty() {
continue;
}
if seen.insert(mirror.clone()) {
mirror_candidates.push(mirror);
}
}

return Ok(mirror_candidates);
}
}

Ok(Vec::new())
}

fn try_next_manual_mirror(
mut metadata: ManualDownloadStatus,
key: (PackageId, String),
manual_downloads: &mut ManualDownloads,
error: DownloadError,
) -> anyhow::Result<()> {
print_to_terminal(
0,
&format!(
"manual_download: got error from mirror {mirror:?} {error:?}, trying next mirror: {next_mirror:?}",
mirror = metadata.active_mirror,
error = error,
next_mirror = metadata.mirrors_left.iter().next().cloned(),
),
);

let (package_id, version_hash) = key.clone();

match metadata.mirrors_left.first().cloned() {
Some(next_mirror) => {
metadata
.mirrors_failed
.push((metadata.active_mirror.clone(), error));
metadata.mirrors_left.remove(0);
metadata.active_mirror = next_mirror.clone();
manual_downloads.insert(key, metadata);

Request::to(("our", "downloads", "app-store", "sys"))
.body(serde_json::to_vec(&DownloadRequest::LocalDownload(
LocalDownloadRequest {
package_id: crate::hyperware::process::main::PackageId::from_process_lib(
package_id,
),
download_from: next_mirror.clone(),
desired_version_hash: version_hash,
},
))?)
.send()?;
}
None => {
Request::to(("our", "main", "app-store", "sys"))
.body(DownloadCompleteRequest {
package_id: crate::hyperware::process::main::PackageId::from_process_lib(
package_id,
),
version_hash,
err: Some(error),
})
.send()?;
}
}

Ok(())
}

/// Try the next available mirror for a download, recording the current mirror's failure
fn try_next_mirror(
mut metadata: AutoUpdateStatus,
Expand Down
4 changes: 2 additions & 2 deletions hyperdrive/packages/contacts/pkg/ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<body class="bg-iris text-white">
<svg id="large-background" viewBox="0 0 393 356" fill="none" xmlns="http://www.w3.org/2000/svg"
class="absolute bottom-0 left-0 w-screen bg-center bg-cover pointer-events-none z-0">
class="absolute bottom-0 left-0 w-screen bg-center bg-cover pointer-events-none z-0 opacity-20">
<g clip-path="url(#clip0_1483_2725)">
<path
d="M339.268 685.316L339.268 535.649L454.813 461.579L454.813 396.535L512.8 359.361C533.086 346.359 559.152 346.359 579.438 359.361L658.066 409.776"
Expand Down Expand Up @@ -91,4 +91,4 @@ <h1 class="text-2xl font-bold prose" style="align-self: start;">Contacts</h1>
<script src="/contacts:contacts:sys/script.js"></script>
</body>

</html>
</html>
Loading