diff --git a/AGENTS.md b/AGENTS.md index 5a044f90..b7bddbae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ | The system toolbox's workflow templates (library, runner, forks, evaluation, `mod.research`) | `crates/toolbox/` (templates in `crates/toolbox/templates//`); offered to app agents by `crates/ai-host/src/toolbox_peers.rs` (feature `toolbox-peers`) | `cargo test --locked -p octosense-toolbox`, its README, `apps.yml` | | A system app | `apps//bundle/` | App Hub's `card-host --bundle apps//bundle --system`; then in a shell | | A system app's agent: its tools and the cards they publish | `apps//bundle/tools.json` and the manifest's `agent` block; the tools run on the app's host service | its host service's tests (below); then in a shell, with the system agent asking the app's agent | -| A host service (`mail`, `calendar`, `news`, `llm`, `model`, `sheet`) | `apps/mail/host-service/`, `apps/calendar/host-service/`, `apps/news/host-service/`, `apps/ai-providers/` (`llm` and `model.complete`), `apps/sheets/host-service/` (the gridcraft engine, ADR 0013) | `cargo test --locked -p octosense-mail-service -p octosense-calendar-service -p octosense-news-service -p octosense-llm-service -p octosense-sheets-service` | +| A host service (`mail`, `calendar`, `news`, `llm`, `model`, `sheet`, `photo`) | `apps/mail/host-service/`, `apps/calendar/host-service/`, `apps/news/host-service/`, `apps/ai-providers/` (`llm` and `model.complete`), `apps/sheets/host-service/` (the gridcraft engine, ADR 0013), `apps/photo/host-service/` (the photocraft engine, ADR 0013) | `cargo test --locked -p octosense-mail-service -p octosense-calendar-service -p octosense-news-service -p octosense-llm-service -p octosense-sheets-service -p octosense-photo-service` | | Connected accounts: OAuth and the `auth`, `github`, `gmail`, `gcalendar` host services; installed apps' Gmail events and the native review of their saves and sends | `crates/oauth-service/` (its README); `crates/shell/src/connected_events.rs`, `crates/shell/src/connected_review.rs` | `cargo test --locked -p octosense-oauth-service` and `cargo test --locked -p octosense-oauth-service --features host`; the two shell files build and test with the shell (from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell`); `apps.yml`, `phone.yml` | | AppCard (opt-in) | `apps/appcard/` | [apps/appcard/AGENTS.md](apps/appcard/AGENTS.md), `apps.yml` | | A native app (App Hub, Rinx, Terminal, Sheets, Reference, AppCard, and the Makepad apps made native: Calculator, Clock, Notes, Reminders, Weather, and Task, which runs only as its own process; and OctosCode, whose `kernel` port the coding scope holds, `crates/shell/src/coding_scope.rs`): its crate, pin, features, hosting per target, sandbox, storage, its tools, which of them the system agent may call (`agent.system_tools`) and which its own agent may (`agent.own_tools`) | `native-apps.json` only; `python3 tools/native_apps.py` writes the marked blocks in the `Cargo.toml`s, `crates/shell/src/native_apps.rs` and `Cargo.lock` | `python3 tools/native_apps.py --check`, `python3 -m unittest discover -s tools -p 'test_*.py'` | diff --git a/Cargo.lock b/Cargo.lock index dde852cf..b54c8684 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,6 +173,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + [[package]] name = "android-build" version = "0.1.4" @@ -794,6 +800,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -964,6 +976,20 @@ name = "bytemuck" version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] [[package]] name = "byteorder" @@ -1015,6 +1041,36 @@ dependencies = [ "pkg-config 0.3.34", ] +[[package]] +name = "cap-primitives" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5f74729fd2f44701d1a8eb47e906cdb3ccd9ec0f02baad85a744b791940b18" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes 3.0.1", + "ipnet", + "maybe-owned", + "rustix", + "rustix-linux-procfs", + "windows-sys 0.60.2", + "winx", +] + +[[package]] +name = "cap-std" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ec78e242cfa2cfe276807ac2ecc00315a6c97786977414bcd1c3963b6c91b8" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes 3.0.1", + "rustix", +] + [[package]] name = "caseless" version = "0.2.2" @@ -2697,6 +2753,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.74.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide 0.8.9", + "num-complex", + "pulp", + "smallvec 1.16.1", + "zune-inflate", +] + [[package]] name = "eyeball" version = "0.8.8" @@ -2904,6 +2976,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "font-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274" +dependencies = [ + "bytemuck 1.25.2", +] + [[package]] name = "fontconfig-parser" version = "0.5.8" @@ -2927,6 +3008,20 @@ dependencies = [ "ttf-parser 0.25.1", ] +[[package]] +name = "fontique" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6688bc1294fe7117d788937b6c53480169b29c566954af490830d4c09da9516a" +dependencies = [ + "hashbrown 0.17.1", + "linebender_resource_handle", + "memmap2", + "parlance", + "read-fonts", + "smallvec 1.16.1", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -2960,6 +3055,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes 2.0.4", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "fs2" version = "0.4.3" @@ -4156,6 +4262,18 @@ dependencies = [ "zerocopy 0.8.57", ] +[[package]] +name = "harfrust" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" +dependencies = [ + "bitflags 2.13.2", + "bytemuck 1.25.2", + "read-fonts", + "smallvec 1.16.1", +] + [[package]] name = "hash32" version = "0.3.1" @@ -4622,11 +4740,32 @@ checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", ] +[[package]] +name = "icu_locale_fallback" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" +dependencies = [ + "icu_locale_core", + "icu_locale_fallback_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" + [[package]] name = "icu_normalizer" version = "2.3.0" @@ -4676,6 +4815,8 @@ checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", + "serde", + "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -4683,6 +4824,28 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_segmenter" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db" +dependencies = [ + "icu_collections", + "icu_locale_fallback", + "icu_provider", + "icu_segmenter_data", + "potential_utf", + "smallvec 1.16.1", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" + [[package]] name = "ident_case" version = "1.0.1" @@ -4734,10 +4897,13 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ "bytemuck 1.25.2", "byteorder-lite", + "color_quant", + "gif", "image-webp", "moxcms", "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.18.1", + "qoi", "tiff", "zune-core", "zune-jpeg", @@ -4858,6 +5024,28 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "io-extras" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20fd6de4ccfcc187e38bc21cfa543cb5a302cb86a8b114eb7f0bf0dc9f8ac00f" +dependencies = [ + "io-lifetimes 3.0.1", + "windows-sys 0.52.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "io-lifetimes" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96" + [[package]] name = "ipnet" version = "2.12.2" @@ -5066,6 +5254,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-encoder" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b454d911ac55068f53495488d8ccd0646eaa540c033a28ee15b07838afafb01f" + [[package]] name = "js-sys" version = "0.3.105" @@ -5209,6 +5403,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "lettre" version = "0.11.23" @@ -5306,6 +5506,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + [[package]] name = "linkify" version = "0.10.0" @@ -6715,6 +6921,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + [[package]] name = "md-5" version = "0.10.6" @@ -7146,6 +7358,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ + "bytemuck 1.25.2", "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -7430,7 +7643,7 @@ dependencies = [ "regex", "reqwest 0.12.28", "reqwest 0.13.5", - "rmcp", + "rmcp 1.8.0", "rustix", "serde", "serde_json", @@ -8357,6 +8570,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "octosense-photo-service" +version = "0.1.0" +dependencies = [ + "octosense-appstore", + "photocraft-automation", + "photocraft-io", + "serde_json", + "tempfile", +] + [[package]] name = "octosense-process-apps" version = "0.1.0" @@ -8434,6 +8658,7 @@ dependencies = [ "octosense-markdown-editor", "octosense-news-service", "octosense-oauth-service", + "octosense-photo-service", "octosense-reference", "octosense-sheets-service", "octosense-wasm-host", @@ -8635,6 +8860,39 @@ dependencies = [ "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parlance" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6937eda350acc1a5d05872c3cbf99fe78619c269096e2be3d4a350058639d5" + +[[package]] +name = "parley" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22d2ff88bd3f7d68d1d9b09c7e6209f9a8e8c05088295140a2bcf2e9b17038c5" +dependencies = [ + "fontique", + "harfrust", + "hashbrown 0.17.1", + "icu_normalizer", + "icu_properties", + "icu_segmenter", + "linebender_resource_handle", + "parlance", + "parley_data", + "skrifa", +] + +[[package]] +name = "parley_data" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1567535334d6ba2d3cde19221ba9a7bd0fabb3cbd99046ddfb10ae061cfcc889" +dependencies = [ + "icu_properties", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -8646,6 +8904,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pastey" version = "0.1.1" @@ -8842,6 +9106,269 @@ dependencies = [ "siphasher", ] +[[package]] +name = "photocraft-algo" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "photocraft-color", + "photocraft-geom", + "photocraft-raster", + "rayon", + "serde", +] + +[[package]] +name = "photocraft-automation" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "base64 0.22.1", + "cap-std", + "getrandom 0.3.4", + "photocraft-codecs", + "photocraft-compose", + "photocraft-doc", + "photocraft-engine", + "photocraft-format", + "photocraft-geom", + "photocraft-io", + "rmcp 3.5.1", + "schemars 1.2.2", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", +] + +[[package]] +name = "photocraft-cms" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "rayon", +] + +[[package]] +name = "photocraft-codecs" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "exr", + "flate2", + "half", + "image", + "image-webp", + "jpeg-encoder", + "png 0.18.1", + "rayon", + "thiserror 2.0.20", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "photocraft-color" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "photocraft-cms", + "serde", +] + +[[package]] +name = "photocraft-compose" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "photocraft-color", + "photocraft-doc", + "photocraft-geom", + "photocraft-raster", + "photocraft-vector", + "rayon", +] + +[[package]] +name = "photocraft-doc" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "photocraft-color", + "photocraft-geom", + "photocraft-raster", + "serde", + "serde_json", +] + +[[package]] +name = "photocraft-engine" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "blake3", + "flate2", + "photocraft-algo", + "photocraft-cms", + "photocraft-codecs", + "photocraft-color", + "photocraft-compose", + "photocraft-doc", + "photocraft-format", + "photocraft-geom", + "photocraft-io", + "photocraft-ops", + "photocraft-paint", + "photocraft-plugins", + "photocraft-psd", + "photocraft-raster", + "photocraft-text", + "photocraft-vector", + "rayon", + "serde", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "photocraft-format" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "blake3", + "flate2", + "photocraft-codecs", + "photocraft-color", + "photocraft-doc", + "photocraft-geom", + "photocraft-raster", + "ruzstd", + "serde", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "photocraft-geom" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "serde", +] + +[[package]] +name = "photocraft-io" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "blake3", + "photocraft-algo", + "photocraft-cms", + "photocraft-codecs", + "photocraft-color", + "photocraft-compose", + "photocraft-doc", + "photocraft-format", + "photocraft-geom", + "photocraft-paint", + "photocraft-psd", + "photocraft-raster", + "photocraft-raw", + "photocraft-text", + "photocraft-vector", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "photocraft-ops" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "photocraft-doc", +] + +[[package]] +name = "photocraft-paint" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "photocraft-color", + "photocraft-geom", + "photocraft-raster", + "serde", +] + +[[package]] +name = "photocraft-plugins" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "photocraft-color", + "photocraft-geom", + "photocraft-raster", + "rayon", + "serde", + "serde_json", + "thiserror 2.0.20", + "wasmi", +] + +[[package]] +name = "photocraft-psd" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "flate2", + "thiserror 2.0.20", +] + +[[package]] +name = "photocraft-raster" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "photocraft-color", + "photocraft-geom", + "serde", +] + +[[package]] +name = "photocraft-raw" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "rayon", +] + +[[package]] +name = "photocraft-text" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "flate2", + "parley", + "photocraft-color", + "photocraft-doc", + "photocraft-geom", + "photocraft-psd", + "photocraft-raster", + "skrifa", +] + +[[package]] +name = "photocraft-vector" +version = "0.3.0" +source = "git+https://github.com/ymote/photocraft.git?rev=eec4af65513b52084926f3c679fc6ac7821cafaf#eec4af65513b52084926f3c679fc6ac7821cafaf" +dependencies = [ + "photocraft-color", + "photocraft-doc", + "photocraft-geom", + "photocraft-raster", + "rayon", +] + [[package]] name = "pico-args" version = "0.5.0" @@ -9022,6 +9549,8 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ + "serde_core", + "writeable", "zerovec", ] @@ -9161,12 +9690,44 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck 1.25.2", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + [[package]] name = "pxfm" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck 1.25.2", +] + [[package]] name = "qrcode" version = "0.14.1" @@ -9460,6 +10021,17 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck 1.25.2", + "font-types", + "once_cell 1.21.4", +] + [[package]] name = "readlock" version = "0.1.11" @@ -9475,6 +10047,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + [[package]] name = "redb" version = "2.6.3" @@ -9855,7 +10433,7 @@ dependencies = [ "process-wrap", "rand 0.10.2", "reqwest 0.13.5", - "rmcp-macros", + "rmcp-macros 1.8.0", "schemars 1.2.2", "serde", "serde_json", @@ -9870,6 +10448,28 @@ dependencies = [ "uuid", ] +[[package]] +name = "rmcp" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5023804cbe0304cbe06197ac16e645a0a0d0ffa2953bf735c15c2fa0213350" +dependencies = [ + "chrono", + "futures", + "indexmap 2.14.2", + "pastey 0.2.3", + "pin-project-lite", + "rmcp-macros 3.5.1", + "schemars 1.2.2", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tokio-util", + "tracing", + "uuid", +] + [[package]] name = "rmcp-macros" version = "1.8.0" @@ -9883,6 +10483,19 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "rmcp-macros" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1da498700030856ba63114bceefa30ba21f5e278cb975e5b9de3529fe842b40" +dependencies = [ + "darling 0.24.1", + "proc-macro2", + "quote", + "serde_json", + "syn 3.0.5", +] + [[package]] name = "rmp" version = "0.8.15" @@ -10309,6 +10922,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell 1.21.4", + "rustix", +] + [[package]] name = "rustls" version = "0.23.44" @@ -10499,6 +11122,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "ruzstd" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a252f5e20f038fe7b4ea53e073e65398d652c864cc162fc77c56c2f13717b888" +dependencies = [ + "twox-hash", +] + [[package]] name = "ryu" version = "1.0.23" @@ -11337,6 +11969,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck 1.25.2", + "read-fonts", +] + [[package]] name = "slab" version = "0.4.12" @@ -11461,6 +12103,16 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "string-interner" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0" +dependencies = [ + "hashbrown 0.15.5", + "serde", +] + [[package]] name = "string_cache" version = "0.8.9" @@ -11880,6 +12532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -12410,6 +13063,12 @@ dependencies = [ "thiserror 2.0.20", ] +[[package]] +name = "twox-hash" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2" + [[package]] name = "type1-encoding-parser" version = "0.1.1" @@ -12950,6 +13609,46 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78693fcdd618e0fc34af59c6b8efa9ac5d58c68df940beff4bedddb6acfe7c27" +dependencies = [ + "spin", + "wasmi_collections", + "wasmi_core", + "wasmi_ir", + "wasmparser 0.228.0", +] + +[[package]] +name = "wasmi_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8be2aa467cf2d29e96ff759472c36eeb44a3c81c67fc9cb76c9a24c519c557" +dependencies = [ + "string-interner", +] + +[[package]] +name = "wasmi_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69372d5fda3ea3d1e0aa6603c7888110e0187e88ea17cd8fc2e2df0a0e1f37fa" +dependencies = [ + "libm", +] + +[[package]] +name = "wasmi_ir" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f17b774caa13c618c7244f1ee51fe23c5e7b8538a471fa46d9949779758aed6" +dependencies = [ + "wasmi_core", +] + [[package]] name = "wasmlab-functions" version = "0.1.0" @@ -12961,6 +13660,16 @@ dependencies = [ "strsim", ] +[[package]] +name = "wasmparser" +version = "0.228.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3" +dependencies = [ + "bitflags 2.13.2", + "indexmap 2.14.2", +] + [[package]] name = "wasmparser" version = "0.258.3" @@ -14100,6 +14809,16 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags 2.13.2", + "windows-sys 0.52.0", +] + [[package]] name = "wit-bindgen" version = "0.57.1" @@ -14439,6 +15158,7 @@ dependencies = [ "displaydoc", "yoke", "zerofrom", + "zerovec", ] [[package]] @@ -14447,6 +15167,7 @@ version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -14565,6 +15286,15 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32 0.3.10", +] + [[package]] name = "zune-jpeg" version = "0.5.15" diff --git a/Cargo.toml b/Cargo.toml index bdd0a20f..bc6ea7e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ "apps/reference", "apps/news/host-service", "apps/sheets/host-service", + "apps/photo/host-service", "apps/mail/host-service", "apps/calendar/host-service", "apps/ai-providers/config", @@ -189,6 +190,10 @@ gridcraft-functions = { git = "https://github.com/ymote/gridcraft.git", rev = "c gridcraft-calc = { git = "https://github.com/ymote/gridcraft.git", rev = "c6b6f4177cbf52aa7a8bac8aa15650a7502926fe" } gridcraft-model = { git = "https://github.com/ymote/gridcraft.git", rev = "c6b6f4177cbf52aa7a8bac8aa15650a7502926fe" } gridcraft-xlsx = { git = "https://github.com/ymote/gridcraft.git", rev = "c6b6f4177cbf52aa7a8bac8aa15650a7502926fe" } +octosense-photo-service = { path = "apps/photo/host-service" } +# The photocraft raster engine (ADR 0013): the pinned ymote fork, one revision. +photocraft-automation = { git = "https://github.com/ymote/photocraft.git", rev = "eec4af65513b52084926f3c679fc6ac7821cafaf" } +photocraft-io = { git = "https://github.com/ymote/photocraft.git", rev = "eec4af65513b52084926f3c679fc6ac7821cafaf" } octosense-mail-service = { path = "apps/mail/host-service" } octosense-calendar-service = { path = "apps/calendar/host-service" } octosense-news-service = { path = "apps/news/host-service" } diff --git a/apps/AGENTS.md b/apps/AGENTS.md index 75061b2c..e821c201 100644 --- a/apps/AGENTS.md +++ b/apps/AGENTS.md @@ -46,6 +46,8 @@ app's own agent and `tools.json`, the system toolbox, `glance.publish` and `cargo test --locked -p octosense-calendar-service -p octosense-news-service`. The sheet engine's (gridcraft behind `sheet.*`, ADR 0013, no bundle yet): `apps/sheets/host-service` and `cargo test --locked -p octosense-sheets-service`. + The photo engine's (photocraft behind `photo.*`, same ADR, no bundle yet): + `apps/photo/host-service` and `cargo test --locked -p octosense-photo-service`. - Declare an app's agent in its manifest and `bundle/tools.json`. Keep the input/output schemas consistent with the executor (octos requires an object output schema), and select the actual risk, sharing and confirmation policy. diff --git a/apps/photo/host-service/Cargo.toml b/apps/photo/host-service/Cargo.toml new file mode 100644 index 00000000..dbaf3126 --- /dev/null +++ b/apps/photo/host-service/Cargo.toml @@ -0,0 +1,24 @@ +# octosense-photo-service — the `photo` host service (ADR 0013). +# +# photocraft's raster engine behind typed `photo.*` methods, through its +# own headless automation layer: open, inspect, convert, run engine +# commands (the 817-command catalog) and render previews, with file access +# bound to the caller's host directory by photocraft's capability-rooted +# AuthorizedWorkspace. Stateless: every call opens, acts and closes. +[package] +name = "octosense-photo-service" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +publish = false + +[dependencies] +# The same App Hub rev as Mail and News: one appstore, one service registry. +octosense-appstore = { workspace = true } +serde_json = { workspace = true } +# The engine, pinned to one ymote/photocraft revision (ADR 0013). +photocraft-automation = { workspace = true } +photocraft-io = { workspace = true } + +[dev-dependencies] +tempfile = "3" diff --git a/apps/photo/host-service/src/lib.rs b/apps/photo/host-service/src/lib.rs new file mode 100644 index 00000000..224fbfdb --- /dev/null +++ b/apps/photo/host-service/src/lib.rs @@ -0,0 +1,202 @@ +//! `octosense-photo-service` — the `photo` host service (ADR 0013). +//! +//! photocraft's engine through its own headless automation layer. Every +//! call is a fresh, stateless session whose file access is bound to the +//! caller's host directory by photocraft's capability-rooted +//! [`AuthorizedWorkspace`] — paths are relative, and separators, `..`, +//! prefixes and escapes are refused by the engine before any I/O. +//! +//! Methods (all under the `photo` family; paths relative to the host dir): +//! - `info {path}` → the document inspected as JSON +//! - `convert {path, out, format?}` → `{out, warnings}` +//! - `run {path, cmds: [{id, params?}], out?, format?}` → command results, +//! saving when `out` is given (the engine's command catalog: crops, +//! adjustments, filters — `photo.commands` lists it) +//! - `commands {}` → the engine's command catalog +//! - `render {path, out, max_side?}` → a PNG preview written to `out` +//! +//! The service serves system apps only until ADR 0013's store capability +//! is designed. + +use std::path::Path; + +use octosense_appstore::services::{register_host_service, HostService, Replier, ServiceCall, ServiceHost}; +use photocraft_automation::headless::Headless; +use photocraft_automation::workspace::AuthorizedWorkspace; +use photocraft_io::ExportOptions; +use serde_json::{json, Value as Json}; + +/// The longest preview edge `render` produces. +const MAX_RENDER_SIDE: u32 = 4096; + +/// Which apps may call the service: system apps, as News and Sheets. +fn may_call(app_id: &str) -> bool { + app_id.starts_with("os.") +} + +pub struct PhotoService; + +/// Register the `photo` service with App Hub's host-service registry. +pub fn register() { + register_host_service(Box::new(PhotoService)); +} + +impl HostService for PhotoService { + fn family(&self) -> &'static str { + "photo" + } + + fn call(&mut self, call: ServiceCall, reply: Replier, _host: &mut dyn ServiceHost) { + if !may_call(&call.app_id) { + reply.send(Err("The photo service serves system apps only.".into())); + return; + } + let method = call.method().to_string(); + let args = call.args.clone(); + let host_dir = call.host_dir.clone(); + reply.send(dispatch(&method, &args, &host_dir)); + } +} + +fn session(host_dir: &Path) -> Result { + let ws = AuthorizedWorkspace::new(Some(host_dir), Some(host_dir)).map_err(|e| format!("photo: {e}"))?; + Ok(Headless::with_workspace(ws)) +} + +fn dispatch(method: &str, args: &Json, host_dir: &Path) -> Result { + match method { + "info" => info(args, host_dir), + "convert" => convert(args, host_dir), + "run" => run(args, host_dir), + "commands" => commands(), + "render" => render(args, host_dir), + other => Err(format!("photo.{other} is not a method of the photo service")), + } +} + +fn arg_path<'a>(args: &'a Json, key: &str) -> Result<&'a str, String> { + args[key].as_str().filter(|s| !s.is_empty()).ok_or_else(|| format!("photo: `{key}` is required")) +} + +fn info(args: &Json, host_dir: &Path) -> Result { + let path = arg_path(args, "path")?; + let mut h = session(host_dir)?; + let opened = h.open(Path::new(path)).map_err(|e| format!("photo.info: {e}"))?; + let mut doc = h.inspect(None).map_err(|e| format!("photo.info: {e}"))?; + doc["warnings"] = opened["warnings"].clone(); + doc["file"] = json!(path); + Ok(doc) +} + +fn convert(args: &Json, host_dir: &Path) -> Result { + let path = arg_path(args, "path")?; + let out = arg_path(args, "out")?; + let format = args["format"].as_str(); + let mut h = session(host_dir)?; + let opened = h.open(Path::new(path)).map_err(|e| format!("photo.convert: {e}"))?; + let saved = h + .save(None, Some(Path::new(out)), format, &ExportOptions::default()) + .map_err(|e| format!("photo.convert: {e}"))?; + Ok(json!({"out": out, "warnings": [opened["warnings"], saved["warnings"]]})) +} + +fn run(args: &Json, host_dir: &Path) -> Result { + let path = arg_path(args, "path")?; + let cmds = args["cmds"].as_array().ok_or("photo.run: `cmds` is a list of {id, params?}")?; + if cmds.len() > 64 { + return Err("photo.run: at most 64 commands per call".into()); + } + let mut h = session(host_dir)?; + h.open(Path::new(path)).map_err(|e| format!("photo.run: {e}"))?; + let mut results = Vec::new(); + for c in cmds { + let id = c["id"].as_str().ok_or("photo.run: each command has an `id`")?; + let params = if c["params"].is_null() { json!({}) } else { c["params"].clone() }; + let r = h.command_run(id, params).map_err(|e| format!("photo.run {id}: {e}"))?; + results.push(json!({"id": id, "result": r})); + } + let saved = match args["out"].as_str() { + Some(out) if !out.is_empty() => { + h.save(None, Some(Path::new(out)), args["format"].as_str(), &ExportOptions::default()) + .map_err(|e| format!("photo.run: {e}"))?; + json!(out) + } + _ => Json::Null, + }; + Ok(json!({"results": results, "out": saved})) +} + +fn commands() -> Result { + Ok(Headless::new().command_list()) +} + +fn render(args: &Json, host_dir: &Path) -> Result { + let path = arg_path(args, "path")?; + let out = arg_path(args, "out")?; + let max_side = args["max_side"].as_u64().unwrap_or(1024).min(MAX_RENDER_SIDE as u64) as u32; + let mut h = session(host_dir)?; + h.open(Path::new(path)).map_err(|e| format!("photo.render: {e}"))?; + let png = h.render_png(None, max_side).map_err(|e| format!("photo.render: {e}"))?; + h.write_render(Path::new(out), &png).map_err(|e| format!("photo.render: {e}"))?; + Ok(json!({"out": out, "bytes": png.len()})) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A 12x8 RGB PNG, two colour bands (written by this test's author + /// once, embedded so the suite needs no fixtures on disk). + const PNG: &str = "89504e470d0a1a0a0000000d494844520000000c000000080802000000428689a60000001d49444154789c6378616383866c725ea021063a2bb279d14310d15911005b9497817c6155610000000049454e44ae426082"; + + fn fixture(dir: &Path) -> String { + let bytes: Vec = (0..PNG.len()).step_by(2).map(|i| u8::from_str_radix(&PNG[i..i + 2], 16).unwrap()).collect(); + std::fs::write(dir.join("in.png"), bytes).unwrap(); + "in.png".into() + } + + #[test] + fn info_convert_run_render_on_a_real_png() { + let dir = tempfile::tempdir().unwrap(); + let host = dir.path(); + let input = fixture(host); + + let doc = dispatch("info", &json!({"path": input}), host).unwrap(); + assert_eq!(doc["width"], json!(12), "{doc}"); + assert_eq!(doc["height"], json!(8)); + + let conv = dispatch("convert", &json!({"path": input, "out": "out.webp"}), host).unwrap(); + assert_eq!(conv["out"], json!("out.webp")); + assert!(host.join("out.webp").metadata().unwrap().len() > 0); + + let ran = dispatch( + "run", + &json!({"path": input, "cmds": [{"id": "filter.blur.gaussianBlur", "params": {"radius": 2.0}}], "out": "blurred.png"}), + host, + ) + .unwrap(); + assert_eq!(ran["results"][0]["id"], json!("filter.blur.gaussianBlur"), "{ran}"); + assert!(host.join("blurred.png").metadata().unwrap().len() > 0); + + let rend = dispatch("render", &json!({"path": input, "out": "prev.png", "max_side": 64}), host).unwrap(); + assert!(rend["bytes"].as_u64().unwrap() > 0); + + let cat = commands().unwrap(); + assert!(cat.as_array().map(|a| a.len() > 500).unwrap_or(false) || cat["commands"].as_array().map(|a| a.len() > 500).unwrap_or(false), "a real catalog"); + } + + #[test] + fn paths_outside_the_workspace_are_refused_by_the_engine() { + let dir = tempfile::tempdir().unwrap(); + let host = dir.path(); + for bad in ["../up.png", "/etc/x.png", "a/../../up.png"] { + assert!(dispatch("info", &json!({"path": bad}), host).is_err(), "{bad}"); + } + } + + #[test] + fn only_system_apps_may_call() { + assert!(may_call("os.photos")); + assert!(!may_call("org.example.app")); + } +} diff --git a/crates/shell/Cargo.toml b/crates/shell/Cargo.toml index 2b8ea3f8..3f559375 100644 --- a/crates/shell/Cargo.toml +++ b/crates/shell/Cargo.toml @@ -78,6 +78,7 @@ octosense-calendar-service = { workspace = true, optional = true } octosense-news-service = { workspace = true, optional = true } # The sheet engine service (ADR 0013): gridcraft behind `sheet.*`, desktop only. octosense-sheets-service = { workspace = true, optional = true } +octosense-photo-service = { workspace = true, optional = true } # App Hub's host-service registry: the `glance` service registers into it. octosense-appstore = { workspace = true, optional = true } # The Card runner's policy resolution and Splash adapter: glance tiles run @@ -150,7 +151,7 @@ app-reference = ["dep:octosense-reference"] app-sheets = ["dep:makepad-sheets"] app-terminal = ["dep:makepad-terminal"] app-appcard = ["dep:octosense-appcard", "octos-core"] -app-hub = ["dep:octosense-app-hub-app", "dep:octosense-appstore", "dep:octosense-app-policy", "dep:octosense-app-hub", "dep:octosense-app-contract", "dep:octosense-mail-service", "dep:octosense-oauth-service", "dep:octosense-markdown-editor", "dep:octosense-news-service", "dep:octosense-sheets-service", "dep:octosense-calendar-service", "octosense-ai-host/llm"] +app-hub = ["dep:octosense-app-hub-app", "dep:octosense-appstore", "dep:octosense-app-policy", "dep:octosense-app-hub", "dep:octosense-app-contract", "dep:octosense-mail-service", "dep:octosense-oauth-service", "dep:octosense-markdown-editor", "dep:octosense-news-service", "dep:octosense-sheets-service", "dep:octosense-photo-service", "dep:octosense-calendar-service", "octosense-ai-host/llm"] app-calculator = ["dep:makepad-calculator"] app-clock = ["dep:makepad-clock"] app-notes = ["dep:makepad-notes"] diff --git a/crates/shell/src/apps.rs b/crates/shell/src/apps.rs index aed4fba3..18c25ed0 100644 --- a/crates/shell/src/apps.rs +++ b/crates/shell/src/apps.rs @@ -269,6 +269,9 @@ pub fn register_host_services() { // desktop only until its binary cost is weighed for the phone. #[cfg(feature = "app-hub")] octosense_sheets_service::register(); + // The photo engine service (ADR 0013): photocraft behind `photo.*`. + #[cfg(feature = "app-hub")] + octosense_photo_service::register(); // Apps' own WebAssembly functions (ADR 0011). #[cfg(feature = "wasm-lab")] crate::wasm_service::register(); diff --git a/native-apps.json b/native-apps.json index b6186f8b..675f3ecf 100644 --- a/native-apps.json +++ b/native-apps.json @@ -105,7 +105,7 @@ "bin": null, "default_features": true, "crate_features": [], - "implies": ["dep:octosense-appstore", "dep:octosense-app-policy", "dep:octosense-app-hub", "dep:octosense-app-contract", "dep:octosense-mail-service", "dep:octosense-oauth-service", "dep:octosense-markdown-editor", "dep:octosense-news-service", "dep:octosense-sheets-service", "dep:octosense-calendar-service", "octosense-ai-host/llm"], + "implies": ["dep:octosense-appstore", "dep:octosense-app-policy", "dep:octosense-app-hub", "dep:octosense-app-contract", "dep:octosense-mail-service", "dep:octosense-oauth-service", "dep:octosense-markdown-editor", "dep:octosense-news-service", "dep:octosense-sheets-service", "dep:octosense-photo-service", "dep:octosense-calendar-service", "octosense-ai-host/llm"], "hosting": { "macos": "module", "windows": "module", "linux": "module", "android": "module", "ios": "module", "ohos": "module" }, "shells": { "desktop": "default", "phone": "default" }, "native_mobile": "always",