diff --git a/.cargo/config.toml b/.cargo/config.toml index 912b5003..0a8c0aba 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,3 +6,8 @@ MAKEPAD_BUNDLE_IDENTIFIER = { value = "dev.makepad.octosense", force = true } # AppCards embeds assets from the pinned sibling framework, including Git builds. OCTOSENSE_WORKSPACE = { value = "..", relative = true } + +# The system apps App Hub's shell crate packs into this build (ADR 0004): +# which apps, and where their bundles are (the pinned OctoSense-System-Apps +# checkout beside this repository). Cargo passes it as an absolute path. +OCTOSENSE_SYSTEM_APPS = { value = "system-apps.json", relative = true } diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml index af94a948..684e7da7 100644 --- a/.github/workflows/runtime.yml +++ b/.github/workflows/runtime.yml @@ -19,7 +19,37 @@ jobs: - name: Compile with the shared runtime working-directory: workspace/OctoSense run: | - cargo check --locked --workspace --features mobile-apps + # The octos kernel is a shell service (feature `octos-core`): the + # default and mobile-apps builds link it, on desktop and Android. + cargo check --locked + cargo check --locked --features mobile-apps + for features in "" "--features mobile-apps"; do + for target in "" "--target aarch64-linux-android"; do + cargo tree --locked $features $target -i octosense-octos-core --depth 0 >/dev/null || { echo "::error::octosense-octos-core is missing ($features $target)"; exit 1; } + done + done + # AppCard's UI (the octosense-appcard module) stays opt-in: the + # default and mobile-apps builds must not link it, and the opt-in + # must still build. + for target in "" "--target aarch64-linux-android"; do + out=$(cargo tree --locked --features mobile-apps $target -i octosense-appcard 2>&1 || true) + grep -q "did not match any packages" <<<"$out" || { echo "::error::octosense-appcard is linked without app-appcard ($target)"; echo "$out"; exit 1; } + done + cargo check --locked --workspace --features mobile-apps,app-appcard + # Hosted Rinx (app-rinx, in default) is the library module only: + # never its standalone entry or its own local kernel (Rinx ADR + # 0007). Its assistant is the shell's app-peers service. + for target in "" "--target aarch64-linux-android"; do + tree=$(cargo tree --locked $target -e features -i rinx) + grep -q 'rinx feature "octosense-module"' <<<"$tree" || { echo "::error::rinx is not linked as a module ($target)"; exit 1; } + if grep -qE 'rinx feature "(standalone|octos-local|octos-remote)"' <<<"$tree"; then + echo "::error::hosted rinx pulls a standalone runtime ($target)"; exit 1 + fi + cargo tree --locked $target -i octosense-app-peers --depth 0 >/dev/null || { echo "::error::octosense-app-peers is missing ($target)"; exit 1; } + done + - name: Android kernel bundling tool + working-directory: workspace/OctoSense + run: python -m unittest tools/test_android_kernel.py - name: Reject divergent runtime sources working-directory: workspace/OctoSense run: | diff --git a/Cargo.lock b/Cargo.lock index 88ba4f23..eb1135e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,18 @@ version = 4 name = "ab_glyph_rasterizer" version = "0.1.8" +[[package]] +name = "accessory" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc86a613208ca7d144ca24f6ec49c5cdfa5c05a46f2f7ab5880ff99371f165de" +dependencies = [ + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "addr2line" version = "0.25.1" @@ -40,6 +52,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "aes" version = "0.8.4" @@ -47,10 +69,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] +[[package]] +name = "aes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.1", +] + [[package]] name = "agent-client-protocol" version = "1.3.0" @@ -92,7 +125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06679e1542356341f4550ccfb16338b64f37f6af70de2105446ef6fbb078234c" dependencies = [ "anyhow", - "derive_more", + "derive_more 2.1.1", "schemars 1.2.2", "serde", "serde_json", @@ -101,18 +134,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.21.4", - "version_check", - "zerocopy 0.8.57", -] - [[package]] name = "aho-corasick" version = "1.1.5" @@ -143,6 +164,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-build" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fc9904ad2ad097c3c1cfe2eacaaf0fc24710936fa9ed941cb310b7c6ed2ab7" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "android_log-sys" version = "0.3.2" @@ -242,6 +272,12 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + [[package]] name = "arbitrary" version = "1.4.2" @@ -251,6 +287,26 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + [[package]] name = "arc-swap" version = "1.9.2" @@ -260,6 +316,12 @@ dependencies = [ "rustversion", ] +[[package]] +name = "archery" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca55ee147b1926dbea904f50fe4902494e97bc742205abbbf10c709e43815f" + [[package]] name = "argon2" version = "0.5.3" @@ -287,6 +349,44 @@ name = "arrayvec" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +dependencies = [ + "serde", +] + +[[package]] +name = "article-core" +version = "0.1.0" +source = "git+https://github.com/hagency-org/Rinx.git?rev=a72e4b003a05ebcb31bc86c346d7dd06cdffbceb#a72e4b003a05ebcb31bc86c346d7dd06cdffbceb" +dependencies = [ + "blake3", + "image", + "makepad-markdown", + "octoscript-ui-l0", + "pulldown-cmark 0.13.4", + "rand 0.8.8", + "serde", + "serde_json", + "unicode-segmentation 1.13.3", + "url", +] + +[[package]] +name = "article-makepad" +version = "0.1.0" +source = "git+https://github.com/hagency-org/Rinx.git?rev=a72e4b003a05ebcb31bc86c346d7dd06cdffbceb#a72e4b003a05ebcb31bc86c346d7dd06cdffbceb" +dependencies = [ + "article-core", + "libc", + "makepad-markdown-widgets", + "makepad-widgets", + "unicode-segmentation 1.13.3", +] + +[[package]] +name = "as_variant" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbc3a507a82b17ba0d98f6ce8fd6954ea0c8152e98009d36a40d8dcc8ce078a" [[package]] name = "ascii" @@ -301,6 +401,24 @@ dependencies = [ "libloading 0.8.9", ] +[[package]] +name = "assign" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-channel" version = "2.5.0" @@ -313,6 +431,32 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb61aea1a7def73ee7c350a184f0e70b32c182344e2e75bf70c9b621b83417fd" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + [[package]] name = "async-io" version = "2.6.0" @@ -360,6 +504,27 @@ dependencies = [ "rustix", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-rx" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82c1a761cd7b772912fd2500f2c0a850c0c72f5f66543038393ac6cfaa3659a" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-signal" version = "0.2.14" @@ -378,6 +543,28 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "async-task" version = "4.7.1" @@ -412,6 +599,15 @@ dependencies = [ "tungstenite 0.28.0", ] +[[package]] +name = "async_cell" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447ab28afbb345f5408b120702a44e5529ebf90b1796ec76e9528df8e288e6c2" +dependencies = [ + "loom", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -503,6 +699,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers 0.3.0", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -518,6 +725,12 @@ dependencies = [ "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" @@ -549,13 +762,28 @@ dependencies = [ name = "bit-set" version = "0.8.0" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bit-vec" version = "0.8.0" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -571,6 +799,9 @@ name = "bitflags" version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +dependencies = [ + "serde_core", +] [[package]] name = "blake2" @@ -621,6 +852,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -643,6 +883,12 @@ dependencies = [ "piper", ] +[[package]] +name = "blurhash" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79769241dcd44edf79a732545e8b5cec84c247ac060f5252cd51885d093a8fc" + [[package]] name = "brotli" version = "8.0.4" @@ -731,6 +977,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bytesize" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7354288c522e7e980fafd2075d63d1285794c3a6a16cdd492f189ea406e5f18b" + [[package]] name = "bzip2" version = "0.5.2" @@ -750,13 +1002,31 @@ dependencies = [ "pkg-config 0.3.34", ] +[[package]] +name = "caseless" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" +dependencies = [ + "unicode-normalization", +] + [[package]] name = "cbc" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "cipher", + "cipher 0.4.4", +] + +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher 0.5.2", ] [[package]] @@ -810,7 +1080,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] @@ -821,6 +1091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cipher 0.5.2", "cpufeatures 0.3.1", "rand_core 0.10.1", ] @@ -831,13 +1102,35 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead", + "aead 0.5.2", "chacha20 0.9.1", - "cipher", - "poly1305", + "cipher 0.4.4", + "poly1305 0.8.0", "zeroize", ] +[[package]] +name = "chacha20poly1305" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb" +dependencies = [ + "aead 0.6.1", + "chacha20 0.10.2", + "cipher 0.5.2", + "poly1305 0.9.1", +] + +[[package]] +name = "charset" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e" +dependencies = [ + "base64 0.22.1", + "encoding_rs", +] + [[package]] name = "chromiumoxide" version = "0.9.1" @@ -940,10 +1233,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", - "inout", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "clap" version = "4.6.6" @@ -968,11 +1272,14 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.9" +version = "4.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19" +checksum = "037e2a1a92236d0aff7e845093f64661d6df4c02c9fcc61a60e9e1d736fa392f" dependencies = [ "clap", + "clap_lex", + "is_executable", + "shlex 2.0.1", ] [[package]] @@ -1020,6 +1327,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "color-eyre" version = "0.6.5" @@ -1080,20 +1393,63 @@ dependencies = [ ] [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "compression-codecs" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9" dependencies = [ - "crossbeam-utils", + "compression-core", + "flate2", + "memchr 2.8.3", ] [[package]] -name = "const-oid" -version = "0.10.2" +name = "compression-core" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" + +[[package]] +name = "comrak" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa3d1ea6b01ce72405fa3f826768f51c7a44917527c89b37856ffdd17ee57e4" +dependencies = [ + "caseless", + "entities", + "finl_unicode", + "jetscii", + "phf 0.13.1", + "phf_codegen 0.13.1", + "rustc-hash 2.1.3", + "smallvec 1.16.1", + "typed-arena", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +[[package]] +name = "const_panic" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9603f79528ece8163c496f8932121cb36cfe46259e9c907bb3d8205139d7caa3" +dependencies = [ + "typewit", +] + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -1175,6 +1531,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1256,6 +1618,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.23" @@ -1266,6 +1637,12 @@ checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" name = "crunchy" version = "0.2.4" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -1283,7 +1660,32 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.3", "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "cssparser" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.3", + "smallvec 1.16.1", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", ] [[package]] @@ -1296,6 +1698,53 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher 0.5.2", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cpufeatures 0.3.1", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto", + "rand_core 0.10.1", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "darling" version = "0.23.0" @@ -1390,12 +1839,61 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +[[package]] +name = "date_header" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c03c416ed1a30fbb027ef484ba6ab6f80e1eada675e1a2b92fd673c045a1f1d" + +[[package]] +name = "deadpool" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e98a7e119cd347f4201e1159b19831029e203e2d8b790547708e8157b4acf1e" +dependencies = [ + "deadpool-runtime", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2657f61fb1dd8bf37a8d51093cc7cee4e77125b22f7753f49b289f831bec2bae" +dependencies = [ + "tokio", +] + +[[package]] +name = "deadpool-sync" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e385cc95d3d582c328b36d1ff90feac061102b001894b555e6b465a2e0eaabbf" +dependencies = [ + "deadpool-runtime", +] + +[[package]] +name = "decancer" +version = "3.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9244323129647178bf41ac861a2cdb9d9c81b9b09d3d0d1de9cd302b33b8a1d" + [[package]] name = "deflate64" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.1", +] + [[package]] name = "defmt" version = "1.1.1" @@ -1427,6 +1925,20 @@ dependencies = [ "thiserror 2.0.20", ] +[[package]] +name = "delegate-display" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e3a499943fd5180aeffcb708164ffb22125ceeccb05491b5297981d37d6249" +dependencies = [ + "impartial-ord", + "itoa", + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1436,6 +1948,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_arbitrary" version = "1.4.2" @@ -1447,13 +1970,33 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", +] + [[package]] name = "derive_more" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "derive_more-impl", + "derive_more-impl 2.1.1", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -1490,6 +2033,7 @@ dependencies = [ "block-buffer 0.12.1", "const-oid", "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -1498,7 +2042,7 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.1", ] [[package]] @@ -1509,10 +2053,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.4.6", "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.3", + "windows-sys 0.61.2", +] + [[package]] name = "dispatch2" version = "0.3.1" @@ -1520,6 +2076,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags 2.13.2", + "block2", + "libc", "objc2", ] @@ -1538,6 +2096,21 @@ dependencies = [ name = "downcast-rs" version = "1.2.1" +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + [[package]] name = "dunce" version = "1.0.5" @@ -1556,9 +2129,41 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" dependencies = [ - "cipher", + "cipher 0.4.4", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "serdect", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.10.1", + "serde", + "sha2 0.11.0", + "signature", + "subtle", + "zeroize", ] +[[package]] +name = "ego-tree" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" + [[package]] name = "either" version = "1.18.0" @@ -1581,6 +2186,15 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +[[package]] +name = "emojis" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1514ced566c94991ed9563258ecf61e311fd773bf0a3f20606830386bf66e3" +dependencies = [ + "phf 0.13.1", +] + [[package]] name = "encoding_rs" version = "0.8.41" @@ -1596,12 +2210,24 @@ dependencies = [ "simdutf8", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + [[package]] name = "endian-type" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -1614,6 +2240,27 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "env_filter" version = "0.1.4" @@ -1718,34 +2365,99 @@ dependencies = [ ] [[package]] -name = "eyre" -version = "0.6.14" +name = "eyeball" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3" +checksum = "d93bd0ebf93d61d6332d3c09a96e97975968a44e19a64c947bde06e6baff383f" dependencies = [ - "autocfg", - "indenter", - "once_cell 1.21.4", + "futures-core", + "readlock", + "readlock-tokio", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "fallible-iterator" -version = "0.3.0" +name = "eyeball-im" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +checksum = "4a565cc1ccb357945f66fb4c611de621c3b4cfe8ad58adcd53936f5501312f36" +dependencies = [ + "futures-core", + "imbl", + "tokio", + "tracing", +] [[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" +name = "eyeball-im-util" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdcd038306bd720751aaf9881dfe952379413f463535ff6ee7c6e7e96067169d" +dependencies = [ + "arrayvec 0.7.8", + "eyeball-im", + "futures-core", + "imbl", + "pin-project-lite", + "smallvec 1.16.1", +] + +[[package]] +name = "eyre" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3" +dependencies = [ + "autocfg", + "indenter", + "once_cell 1.21.4", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fancy-regex" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" +dependencies = [ + "bit-set 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fancy_constructor" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a8211ab12c36b63c269e17873331dc1da196a3edb7a5c79015677ef972da34a" +dependencies = [ + "macroific 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "faster-hex" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +checksum = "04839bdf9d8c10f66806fad16b852fc72aab80873aebc3cb69d85b4fa41543ed" dependencies = [ + "autocfg", + "defmt 0.3.100", "heapless", "serde", ] @@ -1756,6 +2468,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + [[package]] name = "fd-lock" version = "4.0.4" @@ -1776,6 +2494,12 @@ dependencies = [ "simd-adler32 0.3.10", ] +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "filetime" version = "0.2.29" @@ -1792,6 +2516,12 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +[[package]] +name = "finl_unicode" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bb028c8b4148c9ee0cca68fcd9add6044e81d3619f48577ddf13a263d047a2" + [[package]] name = "fixedbitset" version = "0.5.7" @@ -1869,6 +2599,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a" +dependencies = [ + "autocfg", +] + [[package]] name = "fs2" version = "0.4.3" @@ -1885,6 +2624,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.34" @@ -2005,6 +2754,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -2012,6 +2770,15 @@ dependencies = [ "byteorder 1.5.0", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "g2gen" version = "1.2.2" @@ -2040,6 +2807,21 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "312d2295c7302019c395cfb90dacd00a82a2eabd700429bba9c7a3f38dbbe11b" +[[package]] +name = "generator" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ade96dc9003043bce7c035c85a9df5a858bfb2039c5a2e6fdf00f324f6c551" +dependencies = [ + "cc", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "log", + "rustversion", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-result 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2050,6 +2832,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -2822,6 +3623,66 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-timers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4202275d95a142fa209a1e35e91c250a710c5600731372cd3464a39ed01573d6" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "goblin" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17582616a7718cca54cec18e534a76c7c4aec11a8b9a85695712f262fd15a4c8" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "growable-bloom-filter" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d174ccb4ba660d431329e7f0797870d0a4281e36353ec4b4a3c5eab6c2cfb6f1" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "xxhash-rust", +] + [[package]] name = "h2" version = "0.4.19" @@ -2841,6 +3702,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "crunchy 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "zerocopy 0.8.57", +] + [[package]] name = "hash32" version = "0.3.1" @@ -2861,9 +3733,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] [[package]] name = "hashbrown" @@ -2899,14 +3768,17 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.17.1", ] [[package]] @@ -2947,6 +3819,15 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f715f263b6e73aa2322b59057134b4daf56a765cbebc2f6b35e640ffded3361b" +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac 0.13.0", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2956,6 +3837,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "hnsw_rs" version = "0.3.4" @@ -2996,17 +3886,46 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" +[[package]] +name = "hpke" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5324110b02044183df000f0bd7d2d7e61f1000631508627e77f63983b6fdffb" +dependencies = [ + "aead 0.6.1", + "chacha20poly1305 0.11.0", + "getrandom 0.4.3", + "hkdf", + "hybrid-array", + "rand_core 0.10.1", + "sha2 0.11.0", + "subtle", + "x25519-dalek", + "zeroize", +] + [[package]] name = "htmd" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5a1c7113c831fec68cbd79cd8bf281a84e5b6943f51473dc266b0b88a6a017e" dependencies = [ - "html5ever", - "markup5ever_rcdom", + "html5ever 0.38.0", + "markup5ever_rcdom 0.38.0+unofficial", "phf 0.13.1", ] +[[package]] +name = "html5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" +dependencies = [ + "log", + "markup5ever 0.35.0", + "match_token", +] + [[package]] name = "html5ever" version = "0.38.0" @@ -3014,7 +3933,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" dependencies = [ "log", - "markup5ever", + "markup5ever 0.38.0", +] + +[[package]] +name = "html5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" +dependencies = [ + "log", + "markup5ever 0.39.0", +] + +[[package]] +name = "htmlize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e815d50d9e411ba2690d730e6ec139c08260dddb756df315dbd16d01a587226" +dependencies = [ + "memchr 2.8.3", + "pastey 0.1.1", + "phf 0.13.1", + "phf_codegen 0.13.1", + "serde_json", ] [[package]] @@ -3107,7 +4049,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.9", ] [[package]] @@ -3323,6 +4265,7 @@ dependencies = [ "moxcms", "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.18.1", + "tiff", "zune-core", "zune-jpeg", ] @@ -3352,6 +4295,39 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "imbl" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bad832b9b463ed9398b8506488cc2e3b897a9d44f13af115f382aac71f4fec" +dependencies = [ + "archery", + "equivalent 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "imbl-sized-chunks", + "rand_core 0.9.5", + "rand_xoshiro", + "serde_core", + "version_check", + "wide", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0813be332553f857953298749fa19549e8b61b80589757c29b4e2a804fa9c6" + +[[package]] +name = "impartial-ord" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc680e03d1a08bdc1c01b60e0c39723430ced63d8cabd93cee0449b6ba916a25" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "indenter" version = "0.3.4" @@ -3395,17 +4371,27 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding", + "block-padding 0.3.3", "generic-array", ] [[package]] -name = "ipnet" -version = "2.12.2" +name = "inout" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" - -[[package]] +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding 0.4.2", + "hybrid-array", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] name = "is-docker" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3424,25 +4410,58 @@ dependencies = [ "once_cell 1.21.4", ] +[[package]] +name = "is_executable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jetscii" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" + [[package]] name = "jiff" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" dependencies = [ - "defmt", + "defmt 1.1.1", "jiff-core", "jiff-static", "jiff-tzdb-platform", @@ -3459,7 +4478,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" dependencies = [ - "defmt", + "defmt 1.1.1", "log", ] @@ -3585,6 +4604,34 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "js_int" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d937f95470b270ce8b8950207715d71aa8e153c0d44c6684d59397ed4949160a" +dependencies = [ + "serde", +] + +[[package]] +name = "js_option" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7dd3e281add16813cf673bf74a32249b0aa0d1c8117519a17b3ada5e8552b3c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "json5" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733a844dbd6fef128e98cb4487b887cb55454d92cd9994b1bafe004fabbe670c" +dependencies = [ + "serde", + "ucd-trie", +] + [[package]] name = "jsonwebtoken" version = "9.3.1" @@ -3615,11 +4662,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "konst" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d" +dependencies = [ + "const_panic", + "typewit", +] + [[package]] name = "kstring" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b609e7ca5ea38f093c20a4a102335b247221c9643b7a6bc3510f196f99499a9e" +checksum = "7a09b82a7f771ed02dc0dd9b27130a0fa5499fa15ed3027116c1e5e4e591bd9e" dependencies = [ "static_assertions", ] @@ -3636,12 +4693,40 @@ dependencies = [ "smallvec 1.16.1", ] +[[package]] +name = "landlock" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cca98e95f35b29d469dade6724c6f96cec9236640f745a0e99b0334ec320ab1" +dependencies = [ + "enumflags2", + "libc", + "thiserror 2.0.20", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lddtree" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "547a362ad16dead8837d5b30402a81b05bb57c352044389f8748f4f2dbd51914" +dependencies = [ + "fs-err", + "glob", + "goblin", +] + [[package]] name = "lettre" version = "0.11.23" @@ -3666,7 +4751,7 @@ dependencies = [ "tokio", "tokio-rustls", "url", - "webpki-roots", + "webpki-roots 1.0.9", ] [[package]] @@ -3710,9 +4795,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" dependencies = [ "cc", "pkg-config 0.3.34", @@ -3729,6 +4814,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "linkify" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" +dependencies = [ + "memchr 2.8.3", +] + [[package]] name = "linux-keyutils" version = "0.2.5" @@ -3766,15 +4860,28 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "generator", + "scoped-tls 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lopdf" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59fa2559e99ba0f26a12458aabc754432c805bbb8cba516c427825a997af1fb7" dependencies = [ - "aes", + "aes 0.8.4", "bitflags 2.13.2", - "cbc", + "cbc 0.1.2", "ecb", "encoding_rs", "flate2", @@ -3828,6 +4935,12 @@ dependencies = [ "pkg-config 0.3.34", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "mach2" version = "0.4.3" @@ -3837,6 +4950,113 @@ dependencies = [ "libc", ] +[[package]] +name = "macroific" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f276537b4b8f981bf1c13d79470980f71134b7bdcc5e6e911e910e556b0285" +dependencies = [ + "macroific_attr_parse 2.0.0", + "macroific_core 2.0.0", + "macroific_macro 2.0.0", +] + +[[package]] +name = "macroific" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8ab95c98294764c0f1eacebdb5b027849201b71fbc383871fe991a1de7eb83" +dependencies = [ + "macroific_attr_parse 3.1.0", + "macroific_core 3.1.0", + "macroific_macro 3.1.0", +] + +[[package]] +name = "macroific_attr_parse" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad4023761b45fcd36abed8fb7ae6a80456b0a38102d55e89a57d9a594a236be9" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.6.0", + "syn 2.0.119", +] + +[[package]] +name = "macroific_attr_parse" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf9b7bdb92825e6a507392b12f0570ade17480be20f4ca951b9071b40deba1d1" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.7.0", + "syn 3.0.5", +] + +[[package]] +name = "macroific_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a7594d3c14916fa55bef7e9d18c5daa9ed410dd37504251e4b75bbdeec33e3" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.6.0", + "syn 2.0.119", +] + +[[package]] +name = "macroific_core" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc880da2bc00d6d87f748805e242d670e248d1b9b6fcef3190503210b6cf056" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.7.0", + "syn 3.0.5", +] + +[[package]] +name = "macroific_macro" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da6f2ed796261b0a74e2b52b42c693bb6dee1effba3a482c49592659f824b3b" +dependencies = [ + "macroific_attr_parse 2.0.0", + "macroific_core 2.0.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "macroific_macro" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201faf668c264432727fb64e44407bcfcf1f5f8153bf4ec248eb6bbe4a39b144" +dependencies = [ + "macroific_attr_parse 3.1.0", + "macroific_core 3.1.0", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "mailparse" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60819a97ddcb831a5614eb3b0174f3620e793e97e09195a395bfa948fd68ed2f" +dependencies = [ + "charset", + "data-encoding", + "quoted_printable", +] + [[package]] name = "makepad-ai" version = "1.0.0" @@ -3933,7 +5153,7 @@ name = "makepad-draw" version = "2.0.0" dependencies = [ "ab_glyph_rasterizer", - "fxhash", + "fxhash 0.2.1", "makepad-gif", "makepad-live-id", "makepad-math", @@ -3946,6 +5166,7 @@ dependencies = [ "makepad-zune-qoi", "rustybuzz 0.18.0", "sdfer", + "serde", "ttf-parser 0.24.1", "unicode-bidi 0.3.18", "unicode-linebreak", @@ -3997,7 +5218,7 @@ name = "makepad-half" version = "2.7.1" dependencies = [ "cfg-if 1.0.4", - "crunchy", + "crunchy 0.2.4", "num-traits 0.2.19", "zerocopy 0.8.39", ] @@ -4037,6 +5258,7 @@ name = "makepad-live-id" version = "1.0.0" dependencies = [ "makepad-live-id-macros", + "serde", ] [[package]] @@ -4053,6 +5275,38 @@ dependencies = [ "makepad-filesystem-watcher", ] +[[package]] +name = "makepad-markdown" +version = "0.1.0" +dependencies = [ + "blake3", + "comrak", + "emojis", + "html5ever 0.39.0", + "pulldown-cmark 0.13.4", + "unicode-segmentation 1.13.3", + "url", +] + +[[package]] +name = "makepad-markdown-widgets" +version = "0.1.0" +dependencies = [ + "blake3", + "core-foundation 0.10.1", + "image", + "makepad-latex-math", + "makepad-markdown", + "makepad-widgets", + "mermaid-rs-renderer", + "resvg", + "roxmltree 0.21.1", + "syntect", + "tiny-skia", + "ttf-parser 0.24.1", + "unicode-segmentation 1.12.0", +] + [[package]] name = "makepad-math" version = "1.0.0" @@ -4284,7 +5538,8 @@ dependencies = [ "makepad-mbtile-reader", "makepad-pdf-parse", "octoscript-node", - "pulldown-cmark", + "pulldown-cmark 0.12.2", + "serde", "serde_json", "ttf-parser 0.24.1", "unicode-segmentation 1.12.0", @@ -4349,6 +5604,23 @@ dependencies = [ "makepad-zune-core", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" +dependencies = [ + "log", + "tendril 0.4.3", + "web_atoms 0.1.3", +] + [[package]] name = "markup5ever" version = "0.38.0" @@ -4356,8 +5628,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" dependencies = [ "log", - "tendril", - "web_atoms", + "tendril 0.5.1", + "web_atoms 0.2.6", +] + +[[package]] +name = "markup5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" +dependencies = [ + "log", + "tendril 0.5.1", + "web_atoms 0.2.6", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.35.0+unofficial" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8bcd53df4748257345b8bc156d620340ce0f015ec1c7ef1cff475543888a31d" +dependencies = [ + "html5ever 0.35.0", + "markup5ever 0.35.0", + "tendril 0.4.3", + "xml5ever 0.35.0", ] [[package]] @@ -4366,10 +5661,21 @@ version = "0.38.0+unofficial" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "333171ccdf66e915257740d44e38ea5b1b19ce7b45d33cc35cb6f118fbd981ff" dependencies = [ - "html5ever", - "markup5ever", - "tendril", - "xml5ever", + "html5ever 0.38.0", + "markup5ever 0.38.0", + "tendril 0.5.1", + "xml5ever 0.38.0", +] + +[[package]] +name = "match_token" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -4388,32 +5694,359 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] -name = "maybe-async" -version = "0.2.11" +name = "matrix-pickle" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" +checksum = "b3d65d46b7379dd0afa4a42f9b2269821d31afdee0111b5e0d74e3bee03553a0" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "matrix-pickle-derive", + "thiserror 2.0.20", ] [[package]] -name = "md-5" -version = "0.10.6" +name = "matrix-pickle-derive" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "414b5e4c34009f2bc3fe35dd018f25755ca38858096574841c7332f99e2c7e77" dependencies = [ - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.10.7", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "memchr" -version = "2.7.6" - -[[package]] -name = "memchr" +name = "matrix-sdk" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "anymap2", + "as_variant", + "async-channel", + "async-stream", + "async-trait", + "axum", + "backon", + "bytes", + "bytesize", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "event-listener", + "eyeball", + "eyeball-im", + "futures-core", + "futures-util", + "gloo-timers 0.4.0", + "hex", + "http", + "imbl", + "indexmap 2.14.2", + "itertools 0.15.0", + "js_int", + "language-tags", + "matrix-sdk-base", + "matrix-sdk-common", + "matrix-sdk-indexeddb", + "matrix-sdk-sqlite", + "mime", + "mime2ext", + "oauth2", + "oauth2-reqwest", + "percent-encoding", + "pin-project-lite", + "rand 0.10.2", + "reqwest 0.13.5", + "ruma", + "serde", + "serde_html_form", + "serde_json", + "sha2 0.11.0", + "tempfile", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", + "url", + "urlencoding", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-base" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "as_variant", + "async-trait", + "bitflags 2.13.2", + "decancer", + "eyeball", + "eyeball-im", + "futures-util", + "growable-bloom-filter", + "matrix-sdk-common", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "regex", + "ruma", + "serde", + "serde_bytes", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tracing", + "unicode-normalization", +] + +[[package]] +name = "matrix-sdk-common" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "eyeball-im", + "futures-core", + "futures-executor", + "futures-util", + "gloo-timers 0.4.0", + "imbl", + "ruma", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tracing", + "tracing-subscriber", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "matrix-sdk-crypto" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "aes 0.9.3", + "as_variant", + "async-trait", + "bs58", + "byteorder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ctr", + "eyeball", + "futures-core", + "futures-util", + "hkdf", + "hmac 0.13.0", + "itertools 0.15.0", + "js_option", + "matrix-sdk-common", + "pbkdf2 0.13.0", + "rand 0.10.2", + "rmp-serde", + "ruma", + "serde", + "serde_json", + "sha2 0.11.0", + "subtle", + "thiserror 2.0.20", + "time", + "tokio", + "tokio-stream", + "tracing", + "ulid", + "url", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-indexeddb" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "async-trait", + "base64 0.23.1", + "futures-util", + "getrandom 0.4.3", + "gloo-utils", + "hkdf", + "js-sys", + "matrix-sdk-base", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "matrix_indexed_db_futures", + "rmp-serde", + "ruma", + "serde", + "serde-wasm-bindgen", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.20", + "tokio", + "tracing", + "uuid", + "wasm-bindgen", + "web-sys", + "zeroize", +] + +[[package]] +name = "matrix-sdk-sqlite" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "as_variant", + "async-trait", + "deadpool", + "deadpool-sync", + "itertools 0.15.0", + "matrix-sdk-base", + "matrix-sdk-crypto", + "matrix-sdk-store-encryption", + "num_cpus", + "rmp-serde", + "ruma", + "rusqlite", + "serde", + "serde_json", + "serde_path_to_error", + "thiserror 2.0.20", + "tokio", + "tracing", + "vodozemac", + "zeroize", +] + +[[package]] +name = "matrix-sdk-store-encryption" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "base64 0.23.1", + "blake3", + "chacha20poly1305 0.11.0", + "getrandom 0.4.3", + "hkdf", + "hmac 0.13.0", + "pbkdf2 0.13.0", + "rand 0.10.2", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.20", + "zeroize", +] + +[[package]] +name = "matrix-sdk-ui" +version = "0.18.0" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" +dependencies = [ + "as_variant", + "async-rx", + "async-stream", + "async_cell", + "bitflags 2.13.2", + "chrono", + "emojis", + "eyeball", + "eyeball-im", + "eyeball-im-util", + "futures-core", + "futures-util", + "fuzzy-matcher", + "growable-bloom-filter", + "imbl", + "indexmap 2.14.2", + "itertools 0.15.0", + "matrix-sdk", + "matrix-sdk-base", + "matrix-sdk-common", + "mime", + "pin-project-lite", + "ruma", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "tracing", + "unicode-normalization", + "unicode-segmentation 1.13.3", +] + +[[package]] +name = "matrix_indexed_db_futures" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245ff6a224b4df7b0c90dda2dd5a6eb46112708d49e8bdd8b007fccb09fea8e4" +dependencies = [ + "accessory", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "delegate-display", + "derive_more 2.1.1", + "fancy_constructor", + "futures-core", + "js-sys", + "matrix_indexed_db_futures_macros_internal", + "sealed 0.6.0", + "serde", + "serde-wasm-bindgen", + "smallvec 1.16.1", + "thiserror 2.0.20", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_evt_listener", + "web-sys", + "web-time", +] + +[[package]] +name = "matrix_indexed_db_futures_macros_internal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b428aee5c0fe9e5babd29e99d289b7f64718c444989aac0442d1fd6d3e3f66d1" +dependencies = [ + "macroific 2.0.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "maybe-async" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.7.6" + +[[package]] +name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" @@ -4427,6 +6060,32 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mermaid-rs-renderer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb5b469222ce6b9896edf0a969cf68464f5a028ef394fd6643f00bd943df117" +dependencies = [ + "anyhow", + "fontdb", + "json5", + "once_cell 1.21.4", + "regex", + "serde", + "serde_json", + "thiserror 2.0.20", + "ttf-parser 0.25.1", +] + [[package]] name = "metrics" version = "0.24.6" @@ -4480,6 +6139,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime2ext" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc" + [[package]] name = "mime_guess" version = "2.0.5" @@ -4597,7 +6262,7 @@ name = "naga" version = "27.0.3" dependencies = [ "arrayvec 0.7.6", - "bit-set", + "bit-set 0.8.0", "bitflags 2.10.0", "cfg-if 1.0.4", "cfg_aliases 0.2.1", @@ -4826,6 +6491,16 @@ dependencies = [ "url", ] +[[package]] +name = "oauth2-reqwest" +version = "0.1.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234fb5c965bbce983ee5de636a7a51d6a3223da8067ea02f9ab2d2d78ac08be2" +dependencies = [ + "oauth2", + "reqwest 0.13.5", +] + [[package]] name = "objc2" version = "0.6.4" @@ -4842,7 +6517,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", +] + +[[package]] +name = "objc2-authentication-services" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6d6f7dab884a28adaec1012eb3889257a49cc145724e35f93ece2d209f8b25" +dependencies = [ + "block2", "objc2", + "objc2-app-kit", "objc2-foundation", ] @@ -4857,6 +6547,29 @@ dependencies = [ "objc2", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -4870,10 +6583,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.2", + "block2", "objc2", "objc2-core-foundation", ] +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-photos-ui" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdddde9dfb986546746e3115021a85c4a83e1c5e9037b3c865a688fe9bf94ebb" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-ui-kit" version = "0.3.2" @@ -4882,6 +6618,18 @@ checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.13.2", "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-uniform-type-identifiers", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7902ac02859fc1f7045f8b598c63f1ae0cc7efeaa06a9bc9f3d9a3c955974fa4" +dependencies = [ "objc2", "objc2-foundation", ] @@ -4897,8 +6645,8 @@ dependencies = [ [[package]] name = "octos-agent" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "base64 0.22.1", @@ -4914,14 +6662,16 @@ dependencies = [ "globset", "hound", "htmd", + "html5ever 0.35.0", "ignore", "keyring", "lettre", "libc", + "markup5ever_rcdom 0.35.0+unofficial", "metrics", "oauth2", "octos-bus", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "octos-llm", "octos-memory", "octos-plugin", @@ -4942,7 +6692,7 @@ dependencies = [ "tiny_http", "tokio", "tokio-util", - "toml", + "toml 0.8.23", "tracing", "tree-sitter", "tree-sitter-javascript", @@ -4957,7 +6707,6 @@ dependencies = [ [[package]] name = "octos-app" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" dependencies = [ "blake3", "chrono", @@ -4969,9 +6718,11 @@ dependencies = [ "octos-app-render", "octos-app-store", "octos-app-transport", - "octos-core 2.0.3-rc.11 (git+https://github.com/octos-org/octos.git?rev=deb433e9a452c03d8fe7f4f6a26b42785c47ced7)", + "octos-core", "octoscript-node", "octoscript-ui-l0", + "octosense-llm-config", + "octosense-octos-core", "reqwest 0.12.28", "robius-open", "serde", @@ -4984,7 +6735,6 @@ dependencies = [ [[package]] name = "octos-app-render" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" dependencies = [ "streaming-markdown-kit", ] @@ -4992,12 +6742,11 @@ dependencies = [ [[package]] name = "octos-app-store" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" dependencies = [ "chrono", "keyring", "log", - "octos-core 2.0.3-rc.11 (git+https://github.com/octos-org/octos.git?rev=deb433e9a452c03d8fe7f4f6a26b42785c47ced7)", + "octos-core", "serde", "serde_json", "uuid", @@ -5006,34 +6755,32 @@ dependencies = [ [[package]] name = "octos-app-transport" version = "0.1.0" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" dependencies = [ "android_logger", "chrono", "futures-util", "log", - "octos-cli", - "octos-core 2.0.3-rc.11 (git+https://github.com/octos-org/octos.git?rev=deb433e9a452c03d8fe7f4f6a26b42785c47ced7)", + "octos-core", + "octosense-octos-core", "reqwest 0.12.28", "serde", "serde_json", "tokio", "tokio-tungstenite 0.24.0", - "tracing-subscriber", "url", "uuid", ] [[package]] name = "octos-bus" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ - "aes", + "aes 0.8.4", "async-trait", "axum", "base64 0.22.1", - "cbc", + "cbc 0.1.2", "chrono", "chrono-tz", "cron", @@ -5041,7 +6788,7 @@ dependencies = [ "futures", "lru", "metrics", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "reqwest 0.12.28", "serde", "serde_json", @@ -5055,15 +6802,15 @@ dependencies = [ [[package]] name = "octos-cli" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "agent-client-protocol", "argon2", "async-trait", "axum", "base64 0.22.1", - "chacha20poly1305", + "chacha20poly1305 0.10.1", "chrono", "clap", "clap_complete", @@ -5071,6 +6818,7 @@ dependencies = [ "colored", "constant_time_eq 0.3.1", "dirs", + "dunce", "eyre", "flate2", "fs2", @@ -5085,7 +6833,7 @@ dependencies = [ "metrics-exporter-prometheus", "octos-agent", "octos-bus", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "octos-diagnostics", "octos-fleet", "octos-fleet-worker", @@ -5093,6 +6841,7 @@ dependencies = [ "octos-memory", "octos-pipeline", "octos-plugin", + "octos-sandbox", "octos-services", "octos-store", "octos-swarm", @@ -5121,7 +6870,7 @@ dependencies = [ "tokio", "tokio-tungstenite 0.26.2", "tokio-util", - "toml", + "toml 0.8.23", "tower-http", "tracing", "tracing-appender", @@ -5133,22 +6882,8 @@ dependencies = [ [[package]] name = "octos-core" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" -dependencies = [ - "chrono", - "eyre", - "serde", - "serde_json", - "sha2 0.10.9", - "tracing", - "uuid", -] - -[[package]] -name = "octos-core" -version = "2.0.3-rc.11" -source = "git+https://github.com/octos-org/octos.git?rev=deb433e9a452c03d8fe7f4f6a26b42785c47ced7#deb433e9a452c03d8fe7f4f6a26b42785c47ced7" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "chrono", "eyre", @@ -5161,11 +6896,11 @@ dependencies = [ [[package]] name = "octos-diagnostics" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "eyre", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "reqwest 0.12.28", "serde", "serde_json", @@ -5173,11 +6908,11 @@ dependencies = [ [[package]] name = "octos-fleet" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "eyre", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "redb", "rusqlite", "serde", @@ -5189,13 +6924,13 @@ dependencies = [ [[package]] name = "octos-fleet-worker" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "eyre", "octos-agent", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "octos-fleet", "octos-llm", "octos-memory", @@ -5206,8 +6941,8 @@ dependencies = [ [[package]] name = "octos-llm" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "base64 0.22.1", @@ -5215,8 +6950,9 @@ dependencies = [ "eyre", "futures", "jsonwebtoken", + "libc", "metrics", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "redb", "reqwest 0.12.28", "secrecy", @@ -5229,14 +6965,14 @@ dependencies = [ [[package]] name = "octos-memory" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "bincode", "chrono", "eyre", "hnsw_rs", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "redb", "regex", "serde", @@ -5248,8 +6984,8 @@ dependencies = [ [[package]] name = "octos-pipeline" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "chrono", @@ -5257,7 +6993,7 @@ dependencies = [ "futures", "glob", "octos-agent", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "octos-llm", "octos-memory", "octos-plugin", @@ -5270,8 +7006,8 @@ dependencies = [ [[package]] name = "octos-plugin" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "eyre", "metrics", @@ -5282,10 +7018,25 @@ dependencies = [ "which 7.0.3", ] +[[package]] +name = "octos-sandbox" +version = "0.1.0" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" +dependencies = [ + "clap", + "eyre", + "landlock", + "lddtree", + "libc", + "rappct", + "seccompiler", + "windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "octos-services" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "chrono", "dirs", @@ -5293,7 +7044,7 @@ dependencies = [ "flate2", "futures", "octos-bus", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "octos-llm", "reqwest 0.12.28", "serde", @@ -5305,15 +7056,15 @@ dependencies = [ [[package]] name = "octos-store" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "base64 0.22.1", "chrono", "constant_time_eq 0.3.1", "eyre", "getrandom 0.2.17", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", + "octos-core", "redb", "serde", "serde_json", @@ -5325,8 +7076,8 @@ dependencies = [ [[package]] name = "octos-swarm" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" dependencies = [ "async-trait", "chrono", @@ -5343,60 +7094,218 @@ dependencies = [ ] [[package]] -name = "octos-workflows" -version = "2.0.3-rc.11" -source = "git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18#a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18" +name = "octos-workflows" +version = "2.0.3-rc.13" +source = "git+https://github.com/octos-org/octos.git?rev=a6ea8505170735f191a12bb47629a6728415d417#a6ea8505170735f191a12bb47629a6728415d417" +dependencies = [ + "chrono", + "eyre", + "octos-agent", + "octos-core", + "octos-pipeline", + "serde", + "serde_json", + "tokio", + "tracing", +] + +[[package]] +name = "octoscript-makepad" +version = "0.1.0" +dependencies = [ + "octoscript-render", + "octoscript-ui-l0", + "serde_json", +] + +[[package]] +name = "octoscript-node" +version = "0.1.0" + +[[package]] +name = "octoscript-render" +version = "0.1.0" +dependencies = [ + "makepad-script", + "octoscript-node", + "serde_json", +] + +[[package]] +name = "octoscript-ui-l0" +version = "0.1.0" +dependencies = [ + "blake3", + "serde_json", +] + +[[package]] +name = "octoscript-widgets" +version = "0.1.0" +dependencies = [ + "makepad-widgets", + "serde_json", +] + +[[package]] +name = "octosense" +version = "0.1.0" +dependencies = [ + "makepad-ai-services", + "makepad-aichat", + "makepad-app-module", + "makepad-network", + "makepad-photos", + "makepad-sheets", + "makepad-strict-json", + "makepad-studio-protocol", + "makepad-widgets", + "makepad-wm-api", + "makepad-wm-theme", + "octosense-app-hub-app", + "octosense-app-peers", + "octosense-appcard", + "octosense-llm-config", + "octosense-llm-service", + "octosense-mail-service", + "octosense-octos-core", + "octosense-reference", + "rinx", +] + +[[package]] +name = "octosense-app-hub" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=46d67e51b62827a1224b1aacddc2a7b9e69185fc#46d67e51b62827a1224b1aacddc2a7b9e69185fc" +dependencies = [ + "base64 0.22.1", + "blake3", + "ed25519-dalek", + "hex", + "octosense-app-policy", + "rand_core 0.9.5", + "serde", + "serde_json", + "ureq 3.4.2", +] + +[[package]] +name = "octosense-app-hub-app" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=46d67e51b62827a1224b1aacddc2a7b9e69185fc#46d67e51b62827a1224b1aacddc2a7b9e69185fc" +dependencies = [ + "makepad-app-module", + "makepad-widgets", + "makepad-wm-theme", + "octosense-app-hub", + "octosense-app-policy", + "octosense-appstore", + "serde_json", +] + +[[package]] +name = "octosense-app-peers" +version = "0.1.0" +dependencies = [ + "log", + "octosense-octos-core", + "serde_json", + "tokio", + "uuid", +] + +[[package]] +name = "octosense-app-policy" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=46d67e51b62827a1224b1aacddc2a7b9e69185fc#46d67e51b62827a1224b1aacddc2a7b9e69185fc" +dependencies = [ + "blake3", + "makepad-widgets", + "serde", + "serde_json", +] + +[[package]] +name = "octosense-appcard" +version = "0.1.0" +dependencies = [ + "makepad-app-module", + "makepad-widgets", + "octos-app", + "serde_json", +] + +[[package]] +name = "octosense-appstore" +version = "0.1.0" +source = "git+https://github.com/OctoSense-org/OctoSense-App-Hub?rev=46d67e51b62827a1224b1aacddc2a7b9e69185fc#46d67e51b62827a1224b1aacddc2a7b9e69185fc" +dependencies = [ + "makepad-app-module", + "makepad-widgets", + "octoscript-makepad", + "octoscript-widgets", + "octosense-app-hub", + "octosense-app-policy", + "serde_json", +] + +[[package]] +name = "octosense-llm-config" +version = "0.1.0" dependencies = [ + "argon2", + "chacha20poly1305 0.10.1", "chrono", - "eyre", - "octos-agent", - "octos-core 2.0.3-rc.11 (git+https://github.com/OctoSense-org/Octoscript-AppCard.git?rev=a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18)", - "octos-pipeline", + "flate2", + "qrcode", "serde", "serde_json", - "tokio", - "tracing", + "thiserror 2.0.20", + "url", ] [[package]] -name = "octoscript-node" -version = "0.1.0" - -[[package]] -name = "octoscript-ui-l0" +name = "octosense-llm-service" version = "0.1.0" dependencies = [ - "blake3", + "image", + "octosense-appstore", + "octosense-llm-config", + "octosense-octos-core", + "rqrr", "serde_json", + "ureq 2.12.1", ] [[package]] -name = "octosense" +name = "octosense-mail-service" version = "0.1.0" dependencies = [ - "makepad-ai-services", - "makepad-aichat", - "makepad-app-module", - "makepad-network", - "makepad-photos", - "makepad-sheets", - "makepad-strict-json", - "makepad-studio-protocol", - "makepad-widgets", - "makepad-wm-api", - "makepad-wm-theme", - "octosense-appcard", - "octosense-reference", + "base64 0.22.1", + "chrono", + "ego-tree", + "keyring", + "mailparse", + "makepad-android-state", + "makepad-jni-sys", + "octosense-appstore", + "rustls", + "scraper", + "serde_json", + "sha2 0.10.9", + "webpki-roots 1.0.9", ] [[package]] -name = "octosense-appcard" +name = "octosense-octos-core" version = "0.1.0" dependencies = [ - "makepad-app-module", - "makepad-widgets", - "octos-app", + "log", + "octos-cli", + "octosense-llm-config", "serde_json", + "tokio", + "tracing-subscriber", ] [[package]] @@ -5457,6 +7366,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "owo-colors" version = "4.4.0" @@ -5503,6 +7422,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "pastey" version = "0.2.3" @@ -5516,7 +7441,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", +] + +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.3", ] [[package]] @@ -5552,6 +7486,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + [[package]] name = "phf" version = "0.12.1" @@ -5567,21 +7511,41 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros", + "phf_macros 0.13.1", "phf_shared 0.13.1", "serde", ] +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + [[package]] name = "phf_codegen" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator", + "phf_generator 0.13.1", "phf_shared 0.13.1", ] +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.8", +] + [[package]] name = "phf_generator" version = "0.13.1" @@ -5592,19 +7556,41 @@ dependencies = [ "phf_shared 0.13.1", ] +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "phf_macros" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator", + "phf_generator 0.13.1", "phf_shared 0.13.1", "proc-macro2", "quote", "syn 2.0.119", ] +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "phf_shared" version = "0.12.1" @@ -5676,6 +7662,12 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "png" version = "0.17.16" @@ -5716,6 +7708,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "poly1305" version = "0.8.0" @@ -5724,7 +7722,17 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures 0.2.17", "opaque-debug", - "universal-hash", + "universal-hash 0.5.1", +] + +[[package]] +name = "poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" +dependencies = [ + "cpufeatures 0.3.1", + "universal-hash 0.6.1", ] [[package]] @@ -5793,6 +7801,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.15+spec-1.1.0", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -5804,9 +7821,9 @@ dependencies = [ [[package]] name = "process-wrap" -version = "9.1.0" +version = "9.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" +checksum = "f95f6259ebd0640685b7cd26d635861f39208df0f89b2b61ff5cf76310fa54f6" dependencies = [ "futures", "indexmap 2.14.2", @@ -5825,6 +7842,29 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "pulldown-cmark" version = "0.12.2" @@ -5834,6 +7874,25 @@ dependencies = [ "unicase 2.9.0", ] +[[package]] +name = "pulldown-cmark" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags 2.13.2", + "getopts", + "memchr 2.8.3", + "pulldown-cmark-escape", + "unicase 2.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + [[package]] name = "pxfm" version = "0.1.30" @@ -6079,6 +8138,16 @@ dependencies = [ "rustversion", ] +[[package]] +name = "rappct" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11762a6aea3b0a87a51b21cb2085d252f10c01cd0f3f505e1da002cf6e2427da" +dependencies = [ + "thiserror 2.0.20", + "windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "raw-cpuid" version = "11.6.0" @@ -6088,6 +8157,12 @@ dependencies = [ "bitflags 2.13.2", ] +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "rayon" version = "1.12.0" @@ -6108,6 +8183,21 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "readlock" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6da6f291b23556edd9edaf655a0be2ad8ef8002ff5f1bca62b264f3f58b53f34" + +[[package]] +name = "readlock-tokio" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7e264f9ec4f3d112e8e2f214e8e7cb5cf3b83278f3570b7e00bfe13d3bd8ff" +dependencies = [ + "tokio", +] + [[package]] name = "redb" version = "2.6.3" @@ -6137,6 +8227,16 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" +dependencies = [ + "libredox", + "thiserror 2.0.20", +] + [[package]] name = "ref-cast" version = "1.0.27" @@ -6226,7 +8326,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams 0.4.2", "web-sys", - "webpki-roots", + "webpki-roots 1.0.9", ] [[package]] @@ -6239,6 +8339,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -6286,6 +8387,30 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "rfd" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220" +dependencies = [ + "block2", + "dispatch2", + "js-sys", + "libc", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "percent-encoding", + "pollster", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rgb" version = "0.8.53" @@ -6296,17 +8421,98 @@ dependencies = [ ] [[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rinx" +version = "1.0.0-beta.1" +source = "git+https://github.com/hagency-org/Rinx.git?rev=a72e4b003a05ebcb31bc86c346d7dd06cdffbceb#a72e4b003a05ebcb31bc86c346d7dd06cdffbceb" +dependencies = [ + "anyhow", + "arboard", + "article-core", + "article-makepad", + "bitflags 2.13.2", + "blake3", + "blurhash", + "bytesize", + "chrono", + "clap", + "crossbeam-channel", + "crossbeam-queue", + "dispatch2", + "eyeball", + "eyeball-im", + "futures", + "futures-util", + "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "htmlize", + "image", + "imbl", + "indexmap 2.14.2", + "libc", + "linkify", + "makepad-app-module", + "makepad-code-editor", + "makepad-widgets", + "matrix-sdk", + "matrix-sdk-base", + "matrix-sdk-crypto", + "matrix-sdk-ui", + "mime", + "mime_guess", + "num_cpus", + "octoscript-makepad", + "octoscript-ui-l0", + "octoscript-widgets", + "octosense-app-peers", + "octosense-app-policy", + "pulldown-cmark 0.13.4", + "rand 0.8.8", + "rangemap", + "rfd", + "rinx-miniapp-core", + "robius-directories", + "robius-file-picker", + "robius-location", + "robius-open", + "robius-share", + "robius-speech", + "robius-use-makepad", + "robius-web-auth-session", + "ruma", + "sanitize-filename", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "toml 1.1.6+spec-1.1.0", + "tracing-subscriber", + "unicode-segmentation 1.13.3", + "url", + "uuid", + "webpki-root-certs", + "winresource", +] + +[[package]] +name = "rinx-miniapp-core" +version = "0.1.0" +source = "git+https://github.com/hagency-org/Rinx.git?rev=a72e4b003a05ebcb31bc86c346d7dd06cdffbceb#a72e4b003a05ebcb31bc86c346d7dd06cdffbceb" dependencies = [ - "cc", - "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", + "serde", + "serde_json", ] [[package]] @@ -6324,7 +8530,7 @@ dependencies = [ "http-body", "http-body-util", "oauth2", - "pastey", + "pastey 0.2.3", "pin-project-lite", "process-wrap", "rand 0.10.2", @@ -6357,6 +8563,25 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + [[package]] name = "robius-android-env" version = "0.2.0" @@ -6364,13 +8589,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "087fcb3061ccc432658a605cb868edd44e0efb08e7a159b486f02804a7616bef" dependencies = [ "jni 0.21.1", + "makepad-android-state", "ndk-context", ] +[[package]] +name = "robius-common" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" + +[[package]] +name = "robius-directories" +version = "6.0.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" +dependencies = [ + "dirs-sys 0.5.0", + "jni 0.21.1", + "robius-android-env", +] + +[[package]] +name = "robius-file-picker" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" +dependencies = [ + "android-build", + "block2", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "dispatch2", + "jni 0.21.1", + "mime_guess", + "objc2", + "objc2-foundation", + "objc2-photos-ui", + "objc2-ui-kit", + "objc2-uniform-type-identifiers", + "rfd", + "robius-android-env", + "robius-common", + "robius-directories", +] + +[[package]] +name = "robius-location" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" +dependencies = [ + "android-build", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "jni 0.21.1", + "objc2", + "objc2-core-location", + "objc2-foundation", + "robius-android-env", + "windows 0.56.0", + "zbus", +] + [[package]] name = "robius-open" version = "0.3.1" -source = "git+https://github.com/project-robius/robius#71966dc2194307e27d3f9eedc9138614624b4265" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" dependencies = [ "block2", "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6384,6 +8663,58 @@ dependencies = [ "windows 0.56.0", ] +[[package]] +name = "robius-share" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" +dependencies = [ + "android-build", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "dispatch2", + "jni 0.21.1", + "libc", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "robius-android-env", + "robius-common", + "windows 0.56.0", +] + +[[package]] +name = "robius-speech" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius?rev=166991b1b18188ee2d8441f7118721cee2a0c6eb#166991b1b18188ee2d8441f7118721cee2a0c6eb" +dependencies = [ + "jni 0.21.1", + "robius-android-env", + "windows 0.61.3", +] + +[[package]] +name = "robius-use-makepad" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947db6944188cbab806d657f70ec5c55ec9f169d3cab83ac2d04801bd25a2826" +dependencies = [ + "robius-android-env", +] + +[[package]] +name = "robius-web-auth-session" +version = "0.3.1" +source = "git+https://github.com/project-robius/robius#cca3cc3dc99d8fcb6c69a127cd28d47ba8f3e3eb" +dependencies = [ + "block2", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "dispatch2", + "objc2", + "objc2-authentication-services", + "objc2-foundation", + "objc2-ui-kit", +] + [[package]] name = "roxmltree" version = "0.20.0" @@ -6421,6 +8752,16 @@ dependencies = [ "lru", ] +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 2.0.20", +] + [[package]] name = "rtoolbox" version = "0.0.6" @@ -6431,11 +8772,140 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ruma" +version = "0.16.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" +dependencies = [ + "assign", + "js_int", + "js_option", + "ruma-client-api", + "ruma-common", + "ruma-events", + "ruma-html", + "web-time", +] + +[[package]] +name = "ruma-client-api" +version = "0.24.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" +dependencies = [ + "as_variant", + "assign", + "bytes", + "http", + "js_int", + "js_option", + "maplit", + "ruma-common", + "ruma-events", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.20", + "url", + "web-time", +] + +[[package]] +name = "ruma-common" +version = "0.19.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" +dependencies = [ + "as_variant", + "base64 0.22.1", + "bytes", + "date_header", + "form_urlencoded", + "getrandom 0.4.3", + "http", + "indexmap 2.14.2", + "js_int", + "konst", + "percent-encoding", + "rand 0.10.2", + "regex", + "ruma-identifiers-validation", + "ruma-macros", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.20", + "time", + "tracing", + "url", + "uuid", + "web-time", + "wildmatch", + "zeroize", +] + +[[package]] +name = "ruma-events" +version = "0.34.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" +dependencies = [ + "as_variant", + "indexmap 2.14.2", + "js_int", + "js_option", + "pulldown-cmark 0.13.4", + "ruma-common", + "ruma-html", + "ruma-macros", + "serde", + "serde_json", + "thiserror 2.0.20", + "tracing", + "web-time", + "wildmatch", + "zeroize", +] + +[[package]] +name = "ruma-html" +version = "0.8.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" +dependencies = [ + "as_variant", + "html5ever 0.39.0", + "ruma-common", + "tracing", + "wildmatch", +] + +[[package]] +name = "ruma-identifiers-validation" +version = "0.12.1" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" +dependencies = [ + "js_int", + "thiserror 2.0.20", +] + +[[package]] +name = "ruma-macros" +version = "0.19.0" +source = "git+https://github.com/project-robius/ruma.git?branch=tsp#7a912c8579871e72468ee5a4deecc16615e73099" +dependencies = [ + "as_variant", + "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-crate", + "proc-macro2", + "quote", + "ruma-identifiers-validation", + "serde", + "syn 3.0.5", + "toml 1.1.6+spec-1.1.0", +] + [[package]] name = "rusqlite" -version = "0.32.1" +version = "0.40.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3" dependencies = [ "bitflags 2.13.2", "fallible-iterator", @@ -6443,6 +8913,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec 1.16.1", + "sqlite-wasm-rs", ] [[package]] @@ -6714,6 +9185,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck 1.25.2", +] + [[package]] name = "same-file" version = "1.0.6" @@ -6723,6 +9203,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sanitize-filename" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" +dependencies = [ + "regex", +] + [[package]] name = "schannel" version = "0.1.29" @@ -6775,14 +9264,85 @@ name = "scoped-tls" version = "1.0.1" [[package]] -name = "scopeguard" -version = "1.2.0" +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scraper" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f3a24d916e78954af99281a455168d4a9515d65eca99a18da1b813689c4ad9" +dependencies = [ + "cssparser", + "ego-tree", + "html5ever 0.35.0", + "precomputed-hash", + "selectors", + "tendril 0.4.3", +] + +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1a36a382ed65dbcc0ab47fd5e9a94112417ccd34560a392ef3b7b0f0ec39148" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "sdfer" +version = "0.2.1" + +[[package]] +name = "sealed" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sealed" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b68e2ea526d9fb32f23ca8894fb5da9e743f34c2f41701f0501dc8a25c4b343" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "seccompiler" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sdfer" -version = "0.2.1" +checksum = "a4ae55de56877481d112a559bbc12667635fdaf5e005712fd4e2b2fa50ffc884" +dependencies = [ + "libc", +] [[package]] name = "secrecy" @@ -6830,6 +9390,25 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5685b6ae43bfcf7d2e7dfcfb5d8e8f61b46442c902531e41a32a9a8bf0ee0fb6" +dependencies = [ + "bitflags 2.13.2", + "cssparser", + "derive_more 2.1.1", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "new_debug_unreachable", + "phf 0.11.3", + "phf_codegen 0.11.3", + "precomputed-hash", + "servo_arc", + "smallvec 1.16.1", +] + [[package]] name = "semver" version = "1.0.28" @@ -6846,6 +9425,27 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -6877,6 +9477,19 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "serde_html_form" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0346d7a342ab90f405cfc08f25d15075f944f42fcabbc5eac923829fa6d228" +dependencies = [ + "form_urlencoded", + "indexmap 2.14.2", + "itoa", + "serde_core", + "zmij", +] + [[package]] name = "serde_json" version = "1.0.151" @@ -6902,6 +9515,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -6911,6 +9535,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6971,6 +9604,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "sha1" version = "0.10.7" @@ -7051,6 +9703,15 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -7140,6 +9801,9 @@ name = "smallvec" version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -7164,6 +9828,18 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + [[package]] name = "sse-stream" version = "0.2.6" @@ -7215,6 +9891,19 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + [[package]] name = "string_cache" version = "0.9.0" @@ -7228,13 +9917,25 @@ dependencies = [ "serde", ] +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + [[package]] name = "string_cache_codegen" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator", + "phf_generator 0.13.1", "phf_shared 0.13.1", "proc-macro2", "quote", @@ -7300,6 +10001,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident 1.0.24", +] + [[package]] name = "syn" version = "2.0.119" @@ -7342,6 +10054,24 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "syntect" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +dependencies = [ + "bincode", + "fancy-regex", + "flate2", + "fnv", + "once_cell 1.21.4", + "regex-syntax", + "serde", + "serde_derive", + "thiserror 2.0.20", + "walkdir", +] + [[package]] name = "sysctl" version = "0.6.0" @@ -7366,7 +10096,7 @@ dependencies = [ "memchr 2.8.3", "ntapi", "objc2-core-foundation", - "windows 0.57.0", + "windows 0.56.0", ] [[package]] @@ -7393,6 +10123,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "tendril" version = "0.5.1" @@ -7465,6 +10206,20 @@ dependencies = [ "cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.55" @@ -7596,6 +10351,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -7663,9 +10419,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "1.1.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +dependencies = [ + "indexmap 2.14.2", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", ] [[package]] @@ -7677,6 +10448,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -7685,18 +10465,45 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.14.2", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", "winnow 0.7.15", ] +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap 2.14.2", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + [[package]] name = "toml_write" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tower" version = "0.5.3" @@ -7719,12 +10526,17 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ + "async-compression", "bitflags 2.13.2", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -8001,12 +10813,51 @@ dependencies = [ "pom", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "typewit" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "ulid" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947dde63b6d514cc5e044edad4e0ca7261afd1099d16c83d942cb2b2f348689c" +dependencies = [ + "rand 0.10.2", + "web-time", +] + [[package]] name = "uluru" version = "3.1.0" @@ -8144,10 +10995,64 @@ dependencies = [ ] [[package]] -name = "untrusted" -version = "0.9.0" +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell 1.21.4", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "ureq" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" +dependencies = [ + "base64 0.23.1", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots 1.0.9", +] + +[[package]] +name = "ureq-proto" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] [[package]] name = "url" @@ -8162,6 +11067,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "usvg" version = "0.46.0" @@ -8195,6 +11106,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -8237,6 +11154,37 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vodozemac" +version = "0.10.0" +source = "git+https://github.com/matrix-org/vodozemac/?rev=cfc4eee#cfc4eeebdf0953314144a6b709449b0434019ca2" +dependencies = [ + "aes 0.9.3", + "arrayvec 0.7.8", + "base64 0.22.1", + "base64ct", + "cbc 0.2.1", + "chacha20poly1305 0.11.0", + "cipher 0.5.2", + "curve25519-dalek", + "ed25519-dalek", + "getrandom 0.4.3", + "hkdf", + "hmac 0.13.0", + "hpke", + "matrix-pickle", + "prost", + "rand 0.10.2", + "serde", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "subtle", + "thiserror 2.0.20", + "x25519-dalek", + "zeroize", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -8352,12 +11300,30 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm_evt_listener" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc92d6378b411ed94839112a36d9dbc77143451d85b05dfb0cce93a78dab1963" +dependencies = [ + "accessory", + "derivative", + "derive_more 1.0.0", + "fancy_constructor", + "futures-core", + "js-sys", + "smallvec 1.16.1", + "tokio", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wayland-backend" version = "0.3.12" dependencies = [ "downcast-rs", - "scoped-tls", + "scoped-tls 1.0.1", "smallvec 1.16.1", "wayland-sys", ] @@ -8413,9 +11379,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", + "serde", "wasm-bindgen", ] +[[package]] +name = "web_atoms" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" +dependencies = [ + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", +] + [[package]] name = "web_atoms" version = "0.2.6" @@ -8423,9 +11402,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" dependencies = [ "phf 0.13.1", - "phf_codegen", - "string_cache", - "string_cache_codegen", + "phf_codegen 0.13.1", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", ] [[package]] @@ -8453,6 +11432,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + [[package]] name = "webpki-roots" version = "1.0.9" @@ -8493,12 +11481,28 @@ dependencies = [ "libc", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck 1.25.2", + "safe_arch", +] + [[package]] name = "widestring" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" +[[package]] +name = "wildmatch" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29333c3ea1ba8b17211763463ff24ee84e41c78224c16b001cd907e663a38c68" + [[package]] name = "winapi" version = "0.3.9" @@ -8551,12 +11555,15 @@ dependencies = [ [[package]] name = "windows" -version = "0.57.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", ] [[package]] @@ -8577,7 +11584,16 @@ dependencies = [ "windows-collections 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", "windows-future 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-numerics", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -8610,14 +11626,15 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.57.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -8642,6 +11659,17 @@ dependencies = [ "windows-strings 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + [[package]] name = "windows-future" version = "0.3.2" @@ -8657,7 +11685,7 @@ checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-threading", + "windows-threading 0.2.1", ] [[package]] @@ -8671,17 +11699,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -8706,9 +11723,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.57.0" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -8716,15 +11733,10 @@ dependencies = [ ] [[package]] -name = "windows-interface" -version = "0.59.3" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" @@ -8736,6 +11748,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + [[package]] name = "windows-numerics" version = "0.3.1" @@ -8766,6 +11788,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -8782,6 +11813,15 @@ dependencies = [ "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.5.1" @@ -8915,6 +11955,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-threading" version = "0.2.1" @@ -9122,6 +12171,25 @@ dependencies = [ "memchr 2.8.3", ] +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr 2.8.3", +] + +[[package]] +name = "winresource" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +dependencies = [ + "toml 1.1.6+spec-1.1.0", + "version_check", +] + [[package]] name = "winsafe" version = "0.0.19" @@ -9140,6 +12208,35 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "x25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e8131a03190127fb2263afc72b322ecadae46b6ff8c6f399ff5d02f5559af6" +dependencies = [ + "curve25519-dalek", + "rand_core 0.10.1", + "serde", + "zeroize", +] + [[package]] name = "xattr" version = "1.6.1" @@ -9150,6 +12247,16 @@ dependencies = [ "rustix", ] +[[package]] +name = "xml5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494" +dependencies = [ + "log", + "markup5ever 0.35.0", +] + [[package]] name = "xml5ever" version = "0.38.0" @@ -9157,7 +12264,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dc9559429edf0cd3f327cc0afd9d6b36fa8cec6d93107b7fbe64f806b5f2d9" dependencies = [ "log", - "markup5ever", + "markup5ever 0.38.0", ] [[package]] @@ -9166,6 +12273,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "xxhash-rust" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" + [[package]] name = "xz2" version = "0.1.7" @@ -9198,6 +12311,76 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.5", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + [[package]] name = "zerocopy" version = "0.8.39" @@ -9302,7 +12485,7 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ - "aes", + "aes 0.8.4", "arbitrary", "bzip2", "constant_time_eq 0.3.1", @@ -9312,11 +12495,11 @@ dependencies = [ "displaydoc", "flate2", "getrandom 0.3.4", - "hmac", + "hmac 0.12.1", "indexmap 2.14.2", "lzma-rs", "memchr 2.8.3", - "pbkdf2", + "pbkdf2 0.12.2", "sha1", "thiserror 2.0.20", "time", @@ -9392,3 +12575,44 @@ checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ "zune-core", ] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.5", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.5", + "winnow 1.0.4", +] diff --git a/Cargo.toml b/Cargo.toml index 2c5a7824..13bb4164 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,50 +40,109 @@ path = "src/main.rs" [dependencies] # `appcard,maps`: the Octoscript-AppCard widget set (kit, text roles, weather, # finance, matplot) and the map navigation layer the appcard module renders. -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } # The AI services wire only: the WM relays frames between the aichat child and the apps. -makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } # In-process hosting (aicontrol §3): the module contract, the palette applied # into each instance's isolate, and the modules this build links. -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", default-features = false, optional = true } octosense-reference = { path = "apps/reference", optional = true } # The whole Octoscript-AppCard app hosted in a tile, in-process (Phase B of # the AppCard port): octos-app's AppShell — routing brain, cards, kernel agent. +# Not shipped for now: strictly opt-in with `--features app-appcard`, on +# every target (it is in neither `default`, `mobile-apps`, nor the native +# mobile dependencies below). octosense-appcard = { path = "apps/appcard", optional = true } +# App Hub, as every OctoSense shell links it (OctoSense-App-Hub +# crates/app-hub-app): the native store module (`apphub`), the Card runner +# module (`card`) that runs contained script apps, the system apps packed at +# build time from the selection `OCTOSENSE_SYSTEM_APPS` names +# (system-apps.json, .cargo/config.toml), installed apps and their icons. +# Same rev as the Mail and llm host services' octosense-appstore +# (OctoSense-System-Apps): one App Hub source, so one host-service registry. +octosense-app-hub-app = { git = "https://github.com/OctoSense-org/OctoSense-App-Hub", rev = "46d67e51b62827a1224b1aacddc2a7b9e69185fc", optional = true } +# The `mail` host service the Mail system app calls through `host.request`: +# accounts, message lists and send run here, with the passwords the host +# holds; the app never gets a socket or a secret. From the pinned +# OctoSense-System-Apps checkout beside this repository +# (native-apps.lock.json, tools/setup-native.py). +octosense-mail-service = { path = "../OctoSense-System-Apps/apps/mail/host-service", optional = true } +# The `llm` host service the AI providers system app calls: the octos +# kernel's LLM providers, their keys (macOS keychain) and the phone QR, on +# host-owned sheets. The config crate names the kernel's octos home it writes. +octosense-llm-service = { path = "../OctoSense-System-Apps/apps/ai-providers/host-service", optional = true } +octosense-llm-config = { path = "../OctoSense-System-Apps/apps/ai-providers/config", optional = true } +# The octos agent kernel as a shell service (OctoSense-System-Apps +# crates/octos-core): one kernel per process, started when a consumer (AppCard, +# Rinx) connects, configured by the AI providers app, restarted when the +# providers change. Feature `octos-core`, on by default. +octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core", optional = true } +# Apps' assistant access (Rinx ADR 0007): for each module whose declared +# `octos.*` services host policy grants, ONE octos peer owned by the shell's +# system agent and a scoped service injected at module creation +# (OctoSense-System-Apps crates/app-peers). Part of `octos-core`. +octosense-app-peers = { path = "../OctoSense-System-Apps/crates/app-peers", features = ["octos-core"], optional = true } +# Rinx (Matrix chats, Moments, articles and mini apps) as an in-process +# module, built without its standalone entry point or local kernel. Its +# assistant is the shell's, injected at creation: no kernel, no credentials, +# no AppCard. +rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "a72e4b003a05ebcb31bc86c346d7dd06cdffbceb", default-features = false, features = ["octosense-module"], optional = true } # The assistant as a module, seated in the pane in-process: the web build's # only chat, a desktop option. Without the engine feature: no model runtime, # NoModel and the tool console (the child process keeps the real models). -makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", default-features = false, optional = true } # A phone cannot spawn the desktop catalog's Cargo binaries. Link the # available modules into native mobile builds without requiring CLI flags. [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] octosense-reference = { path = "apps/reference" } -octosense-appcard = { path = "apps/appcard" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", default-features = false } +# The octos kernel service: Android runs the APK's bundled liboctos.so +# (MAKEPAD_ANDROID_EXTRA_LIBS, tools/android-kernel.py); iOS has none. +octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core" } +octosense-app-peers = { path = "../OctoSense-System-Apps/crates/app-peers", features = ["octos-core"] } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", default-features = false } [features] -default = [] +# App Hub is part of the standard launcher: the store, the Card runner and +# the system apps (News, Photos, Maps, Camera, Mail, AI providers) it runs; +# the octos kernel is a shell service in every standard build. +default = ["app-hub", "octos-core", "app-rinx"] # Also usable on desktop for testing the same embedded apps with --module. -mobile-apps = ["app-reference", "app-sheets", "app-photos", "app-appcard"] +# Photos is the system app here; `app-photos` links Makepad's native Photos +# module instead, for comparison (a linked module of the same id wins). +# AppCard is not among them: it ships only with `--features app-appcard`. +mobile-apps = ["app-reference", "app-sheets", "app-hub", "octos-core"] +# The octos kernel as a shell service (octosense-octos-core): configured at +# startup, its profile written and the kernel restarted by the `llm` service, +# AppCard and other consumers connect to it. Opt out with +# `--no-default-features` plus the features you still want; Android and iOS +# builds always link it. +# App Hub and the host services its system apps call. +app-hub = ["dep:octosense-app-hub-app", "dep:octosense-mail-service", "dep:octosense-llm-service", "dep:octosense-llm-config"] app-reference = ["dep:octosense-reference"] # Sheets linked in as a module (hosting is still Process unless switched). app-sheets = ["dep:makepad-sheets"] # Photos (the picture wall) linked in as a module, the same way. app-photos = ["dep:makepad-photos"] -# The AppCard module (one L0 weather card in a tile) linked in. -app-appcard = ["dep:octosense-appcard"] +octos-core = ["dep:octosense-octos-core", "dep:octosense-app-peers", "octosense-llm-service?/octos-core"] +# The AppCard module (the assistant in a tile) linked in; it connects to the +# shell's octos kernel, so it implies `octos-core`. Opt-in only, on desktop +# and native mobile alike: AppCard is not shipped by default for now (it +# interfered with the other apps). +app-appcard = ["dep:octosense-appcard", "octos-core"] # The aichat module linked in (the pane seats it in-process where there are # no processes, or when `aichat` is switched to module hosting). app-aichat = ["dep:makepad-aichat"] +# Rinx linked in as a module; its assistant is the shell's (it implies +# `octos-core`). Rinx never starts a kernel of its own here. +app-rinx = ["dep:rinx", "octos-core"] [workspace] members = [".", "apps/reference", "apps/appcard"] @@ -110,6 +169,7 @@ panic = "unwind" # Resolve the exact framework sources prepared by tools/setup-native.py. [patch."https://github.com/OctoSense-org/makepad.git"] +makepad-script = { path = "../makepad/platform/script" } makepad-widgets = { path = "../makepad/widgets" } makepad-code-editor = { path = "../makepad/code_editor" } makepad-ai = { path = "../makepad/libs/makepad_ai" } @@ -123,12 +183,35 @@ makepad-wm-theme = { path = "../makepad/libs/wm_theme" } makepad-sheets = { path = "../makepad/apps/sheets" } makepad-photos = { path = "../makepad/apps/photos" } makepad-aichat = { path = "../makepad/apps/aichat" } +# Rinx's article editor: the Markdown and math components. +makepad-markdown = { path = "../makepad/libs/markdown" } +makepad-markdown-widgets = { path = "../makepad/libs/markdown_widgets" } [patch."https://github.com/OctoSense-org/Octoscript.git"] octoscript-ui-l0 = { path = "../octoscript/crates/octoscript-ui-l0" } [patch."https://github.com/OctoSense-org/Octoscript-Makepad.git"] +octoscript-makepad = { path = "../octoscript-makepad/crates/octoscript-makepad" } +octoscript-widgets = { path = "../octoscript-makepad/crates/octoscript-widgets" } octoscript-node = { path = "../octoscript-makepad/crates/octoscript-node" } [patch."https://github.com/ZhangHanDong/makepad.git"] makepad-widgets = { path = "../makepad/widgets" } + +# Rinx names makepad by the kevinaboos URL; it resolves to the one makepad here. +# (Its OctoSense-org URL without `.git` is the same source as the table above.) +[patch."https://github.com/kevinaboos/makepad"] +makepad-widgets = { path = "../makepad/widgets" } +makepad-code-editor = { path = "../makepad/code_editor" } + + +# Rinx takes the app-peers contract from OctoSense-System-Apps by git: the +# same checkout this workspace links, so one broker contract and one kernel. +[patch."https://github.com/OctoSense-org/OctoSense-System-Apps.git"] +octosense-app-peers = { path = "../OctoSense-System-Apps/crates/app-peers" } +octosense-octos-core = { path = "../OctoSense-System-Apps/crates/octos-core" } +octosense-llm-config = { path = "../OctoSense-System-Apps/apps/ai-providers/config" } + +# Rinx's Matrix patches (see Rinx's Cargo.toml). +[patch."https://github.com/ruma/ruma"] +ruma = { git = "https://github.com/project-robius/ruma.git", branch = "tsp" } diff --git a/README.md b/README.md index 06e2667d..178085cc 100644 --- a/README.md +++ b/README.md @@ -1,246 +1,346 @@ -# OctoSense +# OctoSense Desktop -## Shared Octoscript-Makepad runtime +English | [简体中文](README.zh-CN.md) -`native-runtime.lock.json` selects one -[Octoscript-Makepad](https://github.com/OctoSense-org/Octoscript-Makepad) -release. Its `runtime.json` owns the exact Makepad and Octoscript revisions, -shared with AppCards, Mail and the other OctoSense applications. +OctoSense-Desktop is the desktop shell of [OctoSense](https://github.com/OctoSense-org), the agent shell on top of your operating system. It is one Makepad window that is the desktop: a launcher, a dock and tiles, hosting system apps and App Hub store apps as contained script programs, trusted native modules in-process, and Makepad developer programs as child processes. It gets its apps the same way the phone shell, OctoSense-ROM's `home/`, does. -Before building, run `python3 tools/setup-native.py` (Python 3.9+). The framework -repositories are siblings of this app: `../octoscript-makepad`, `../makepad` -and `../octoscript`. Local changes are preserved; `--update` only updates clean -checkouts. CI verifies the selected release and rejects duplicate Makepad sources. -Use `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` -to check the local dependency graph. Existing platform rendering backends remain -part of their applications; the framework controls the shared VM and UI sources. +**Building an OctoSense app?** You do not need this repository to build, check or publish one: start at the [OctoSense-org profile](https://github.com/OctoSense-org)'s reading list (OctoScript-App-Design-Flow's `AGENTS.md`, then `docs/QUICKSTART.md`). Build this shell only if you want to see your app in the desktop shell before it is published ([Try your own app](#try-your-own-app-before-it-is-published)). +## Where it sits -A Makepad desktop that hosts compatible applications inside one window. The shell comes from Makepad's WM app; framework libraries remain external Cargo dependencies pinned to the same upstream commit. +| Repository | Role for this repo | +| --- | --- | +| [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM) | The phone shell (`home/`). Same app model, same runtime, same system apps. | +| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | News, Photos, Maps, Camera, Mail and AI providers bundles, the Mail and `llm` host services, the octos kernel service (`crates/octos-core`) and the AppCard assistant (`octos-app`, opt-in, not shipped by default). Pinned sibling checkout. | +| [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | The signed catalog, the store and the Card runner. Linked as the Git crate `octosense-app-hub-app`. | +| [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | Where apps are designed, built and published to the App Hub. | +| [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | The runtime release that pins Makepad and OctoScript. Pinned sibling checkout. | +| [makepad (OctoSense fork)](https://github.com/OctoSense-org/makepad) | The framework. Pinned sibling checkout. | +| [octos](https://github.com/octos-org/octos) | The agent kernel, a shell service (`octos-core`, on by default): AI providers configures it, AppCard and other consumers connect to it. One revision (`6ad76e5c`, the one System-Apps pins), in `Cargo.lock`; the kernel itself is a separate binary (desktop: `OCTOS_APP_CORE_BIN`; Android: bundled `liboctos.so`). | -## Run +## Repository layout -Install stable Rust and the native development tools for your OS. On macOS, install Xcode Command Line Tools (`xcode-select --install`) if needed. Validated with Rust 1.98.1 on macOS. +| Path | What it is | +| --- | --- | +| `src/` | The shell (crate `octosense`): tiling, launcher, dock, bar, hosting (`host.rs`, `hub.rs`, `module_host.rs`), app registry (`apps.rs`). | +| `src/shell/` | Bar, launcher, menus, notifications, AI pane, gallery. | +| `src/octosense/` | OctoSense-specific parts: app catalog loading, state paths, styles, Makepad source resolution. | +| `apps/reference/` | `octosense-reference`, a small counter/text-input app that runs both as a hosted process and as a linked module. | +| `apps/appcard/` | `octosense-appcard`, the module that mounts the AppCard assistant (`octos-app`) in a tile. Opt-in (`app-appcard`); not shipped for now. | +| `config/apps.json` | The default developer-program catalog. `apps.makepad.json` is an identical copy for `--apps`; `apps.overlay.json` holds the adaptations applied when regenerating them. | +| `system-apps.json` | Which system apps this build packs, and from where. | +| `native-runtime.lock.json` | The OctoScript-Makepad release (and through it, Makepad and OctoScript). | +| `native-apps.lock.json` | The OctoSense-System-Apps revision. | +| `runtime-patches.lock.json` | Reviewed patches on top of the pinned Makepad, with their expected trees. Empty today: the pinned Makepad carries everything the shell needs. | +| `tools/setup-native.py` | Prepares and checks the pinned sibling checkouts. | +| `scripts/` | `upstream.py` (WM provenance and catalog regeneration), `smoke.py` (native smoke test), their Python tests, `system_apps_remote.sh` and `ai_providers_remote.sh` (hidden `--remote` end-to-end runs of the system apps and of AI providers), and `provision-appcard-llm.sh` (Android). | +| `upstream/makepad.json` | Provenance of every file imported from Makepad's `apps/wm`. | +| `resources/` | Themes, wallpapers, icons, Android manifest template, startup script. | +| `docs/` | [Validation record](docs/validation.md), [upstream sync](docs/upstream.md), [local AI](docs/local-ai.md), [Android AppCard build](docs/android-appcard-build.md), dated plans. | +| `KEYBINDINGS.md`, `BACKLOG.md` | Keymap notes; open follow-ups. | -From this directory: +## Prerequisites -```sh -cargo run -``` +- Stable Rust (`cargo` in `~/.cargo/bin`) and the native toolchain for your OS. On macOS, Xcode Command Line Tools (`xcode-select --install`). +- Git and Python 3.9+ for `tools/setup-native.py`. `scripts/upstream.py` needs Python 3.11+ (it imports `tomllib`). +- Network access for the first setup and build. + +## Set up the sibling workspace -Open **Apps → Reference** from the top-left menu. The reference app has a counter and text input, and runs as a separate process inside a tile. Its first launch builds the release executable; build progress appears in the tile. Later launches reuse Cargo's build cache. To build both executables ahead of time: +The build resolves Makepad, OctoScript, OctoScript-Makepad and OctoSense-System-Apps as **siblings** of this checkout (`../makepad`, and so on, through `[patch]` entries in `Cargo.toml`). Put this repository in its own workspace directory and let the setup script fill it in: ```sh -cargo build --release --workspace -cargo run --release +mkdir octosense-ws && cd octosense-ws +git clone https://github.com/OctoSense-org/OctoSense-Desktop.git +cd OctoSense-Desktop +python3 tools/setup-native.py ``` -Prepare the sibling framework sources with the setup command above; the first build downloads any remaining dependencies. The host and Reference app need no Studio process, model download, or wallpaper download. The default apps resolve through Cargo's dependency graph and build on first launch from the same Makepad checkout as the host; unavailable apps are hidden. Fonts and other framework resources are read from that checkout during source development, so keep it available. +Result: -On macOS, `.cargo/config.toml` sets the native menu-bar name to **OctoSense**. -Makepad otherwise derives it from the checkout directory, which may still be -named `makeos`. Rebuild and relaunch after updating; Cargo regenerates the -development `Info.plist` automatically. +```text +octosense-ws/ + OctoSense-Desktop/ this repository + octoscript-makepad/ the release native-runtime.lock.json selects + makepad/, octoscript/ the revisions that release's runtime.json pins + OctoSense-System-Apps/ the revision native-apps.lock.json pins +``` -The build target selects the startup shell automatically: desktop/web builds start with **OctoSense Light** and its bundled wallpaper, Android uses the Android phone layout, and iOS uses the iOS phone layout. You can still switch styles from the shell's style menu. Native phone toolbars are 48 points high and respect the window's safe-area insets. +| Command | Effect | +| --- | --- | +| `python3 tools/setup-native.py` | Clone missing siblings at their pinned revisions (and apply any patch `runtime-patches.lock.json` names). | +| `python3 tools/setup-native.py --check` | Verify the siblings without changing anything. | +| `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` | Also check the locked Cargo graph: one Makepad, one octos, one App Hub. | +| `python3 tools/setup-native.py --update` | Move clean checkouts to the locked revisions (after the lock files change). | +| `--root DIR`, `--cache DIR` | Use another workspace directory; reuse local Git object caches. | -The default desktop starts empty. AI assistant startup, background app prewarming, demo filesystem generation, and wallpaper downloads are off. **System → Quit OctoSense** closes the desktop and its hosted processes. +Local changes in a sibling are preserved; `--update` only moves clean checkouts. When `runtime-patches.lock.json` names a patch, a clean checkout of the commit it was cut from (`source_commit`) is accepted as the same tree. -Selecting Omarchy uses a bundled Tokyo Night wallpaper, so its background also works offline on a fresh install. Installed images in `~/.octosense/wm/themes/tokyo-night/backgrounds/` take precedence. Use `cargo run -- --download-wallpapers` to fetch the theme’s full wallpaper set; **⌘CtrlSpace** cycles installed backgrounds. Asset provenance is in [resources/wallpapers/README.md](resources/wallpapers/README.md). +## Build and run -Eight desktop styles are available. The default **OctoSense** is a floating desktop with Liquid Glass window frames, dock, bar and popups. Click **Light / Dark** beside the style name in the top bar to switch appearances, or press **⌘Space** and search for **OctoSense Dark** directly. Light uses pearl and pale aqua surfaces with dark text; Dark keeps the ink-blue palette. Both include matching versions of **Abyssal Currents**, the original oceanic wallpaper, and rounded hosted surfaces. The wallpaper switches with the appearance, fills the window with a centered crop and works offline. Android retains its animated background. +From this directory, after setup: -Use **⌘Space** for the menu, **⌘W** to close a tile, **⌘F** for tile fullscreen, **⌘1…0** to switch workspaces, and **⌘Shift1…0** to move the focused tile. The menu's **Learn → Keybindings** lists the inherited bindings; shortcuts for apps absent from your catalog report that the app is unavailable. +```sh +cargo run --release +``` -## Android +The desktop starts empty. Start App Hub, a system app or a developer program from the dock, the top-left **Apps** menu, or **⌘Space** (menu and search). **System → Quit OctoSense** closes the desktop and everything it hosts. -With the Makepad Android toolchain installed and a device connected through ADB: +Developer programs from `config/apps.json` build on first launch (progress shows in the tile). To build the workspace ahead of time: ```sh -cargo makepad android run -p octosense --release +cargo build --release --workspace ``` -The Android launcher label is **OctoSense** and its application ID is `dev.makepad.octosense`. It installs separately from an existing MakeOS Android app because the application ID changed. - -`run` builds, installs, and launches the app; `build` only creates the APK. -Native Android/iOS builds automatically link **Reference, Sheets, and Photos** -as embedded apps. They need no extra feature flags after runtime setup. On an -installed device, the launcher derives its default catalog from those linked -modules. Missing Clock/Weather tiles give their space to the available app icons. - -The phone build also links **AppCard** (`apps/appcard`, feature `app-appcard` -on desktop): the whole Octoscript-AppCard app, hosted in-process in a wide -home tile. The module takes `octos-app` — the crate the standalone AppCard APK -is built from — as a git library and mounts its `AppShell` widget: the routing -brain, the card store and transport, the L0 lowering pipeline, sessions, the -composer and the kernel agent all run inside the tile's isolate. The kernel -(`liboctos.so serve --stdio`) is spawned from this APK's native library dir -when it is bundled (`MAKEPAD_ANDROID_EXTRA_LIBS="liboctos.so="` at -build time); without it the app falls back to its WebSocket transport / login -screen. `ask` is the module's one AI-bus tool (the composer). On a desktop, -`cargo run --features app-appcard -- --module appcard` opens the same app; -`OCTOS_APP_CORE_BIN` / `OCTOS_APP_CORE_DIR` point it at a local kernel. -GPS reaches the app through the buildtool activity described below; -notifications, share and the WebView overlay (the standalone APK's other -Java features) are not wired to the hosted shell yet. - -To get AppCard's Java activity features (GPS, notifications, share and -deep-link intents), this repository's `resources/android/AndroidManifest.xml.template` -and the octos kernel bundled as `liboctos.so`, build with the fork's buildtool -`cargo-makepad` and `MAKEPAD_ANDROID_EXTRA_LIBS` instead of the stock command -above — the full recipe, including the kernel cross-build, is in -[docs/android-appcard-build.md](docs/android-appcard-build.md): +| Platform | Status | +| --- | --- | +| macOS | Supported and validated (source builds, process hosting, App Hub, system apps). | +| Windows, Linux | Code paths are retained from upstream but not validated here. | +| Android | `cargo makepad android run -p octosense --release`; see [Phones](#phones). | +| iOS | Startup policy is tested, but a full build currently fails in the pinned Makepad Metal backend ([validation](docs/validation.md)). | -```sh -MAKEPAD_ANDROID_EXTRA_LIBS="liboctos.so=/abs/path/to/octos/target/aarch64-linux-android/release/octos" \ - /abs/path/to/makepad-buildtool/target/debug/cargo-makepad makepad android run -p octosense --release -``` +A relocatable `.app`, installers and a Linux session compositor are not provided. -When the AppCard tile starts, the hosted app spawns that kernel and logs -`stdio: octos=… HOME=…` to logcat; an APK built without it logs `stdio: -bundled octos not found under …; using WebSocket transport` and shows the -app's login screen instead. +### Cargo features -Switching desktop OctoSense to the Android style changes its interface; it still -uses desktop process hosting and the full desktop catalog. The other desktop -apps (including Browser, Files, Terminal, and AI Chat) need embedded mobile -implementations before they can be bundled in the phone build. Photos includes -the app, not your desktop photo library; local Qwen weights are not packaged. +| Feature | Default | Effect | +| --- | --- | --- | +| `app-hub` | on | Links `octosense-app-hub-app` (store `apphub`, Card runner `card`, system apps) and the host services `octosense-mail-service` (Mail) and `octosense-llm-service` (AI providers). Without it the build has no App Hub and no system apps. | +| `app-reference` | off | Links Reference as a module. | +| `app-sheets` | off | Links Makepad's Sheets as a module. | +| `app-photos` | off | Links Makepad's native Photos module; it replaces the Photos system app of the same id (for comparison). | +| `app-appcard` | off | Links the AppCard assistant module (`apps/appcard`); implies `octos-core`. Opt-in on every target, phones included; not shipped for now. | +| `octos-core` | on | The octos kernel service (`octosense-octos-core`, from `../OctoSense-System-Apps/crates/octos-core`): the one kernel AppCard and other consumers share, configured by AI providers. Always on for Android and iOS. Leave it out with `--no-default-features --features app-hub` (and whatever else you want). | +| `app-aichat` | off | Links Makepad's AI chat as a module, without its model engine. | +| `app-rinx` | off | Links [Rinx](https://github.com/upstreamlabs/Rinx), the Matrix client, as a module. | +| `mobile-apps` | off | `app-reference` + `app-sheets` + `app-hub` + `octos-core`: the set phone builds link, for testing on desktop. Not AppCard. | -To exercise the same embedded apps on desktop: +A linked module opens with `--module ` (or a `: Module` line in `wm/apps.splash` under the state directory): ```sh -cargo run --features mobile-apps -- --module reference --module sheets --module photos +cargo run --release --features app-appcard -- --module appcard +cargo run --release --features mobile-apps -- --module reference --module sheets +cargo run --release --features app-rinx -- --module rinx ``` -Reference shares its counter and text-input view between the standalone desktop -process and the embedded mobile module. Sheets and Photos remain external Git -crates at the same pinned Makepad revision. +App Hub's modules are the exception: they have no process form and always open in-process. -Mobile app support is still partial: Sheets needs grid-label and toolbar fixes, -and Photos needs a picture library/import setup. These follow-ups are tracked -in [BACKLOG.md](BACKLOG.md). +### Flags and environment -The iOS startup policy is covered by tests, but a complete iOS build currently -fails in the pinned Makepad Metal backend; see [validation](docs/validation.md). +| Name | Effect | +| --- | --- | +| `--apps ` | Use this developer-program catalog. | +| `--module ` | Host a linked module in-process. | +| `--assistant`, `--prewarm` | Start the assistant app / prewarm apps (need matching catalog entries). Off by default. | +| `--demo-home`, `--download-wallpapers` | Generate a demo filesystem; fetch the Omarchy theme's full wallpaper set. | +| `OCTOSENSE_HOME` | State directory (default `~/.octosense`; falls back to an existing `~/.makeos`, and `MAKEOS_HOME`). | +| `OCTOSENSE_APP_DATA` | Where App Hub keeps installed apps (default `apps/` in the platform data directory). | +| `OCTOSENSE_HUB`, `OCTOSENSE_HUB_ANCHOR` | App Hub catalog origin (path or URL) and trust anchor; default is the App Hub repository's `main`. | +| `OCTOSENSE_SYSTEM_APPS` | The system-app selection file; `.cargo/config.toml` sets it to `system-apps.json`. | +| `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` | Serve Mail's demo mailbox (see [Demos](#demos)). | +| `OCTOSENSE_MAIL_VAULT=file` | Keep Mail passwords in a 0600 file instead of the macOS keychain. | +| `OCTOSENSE_LLM_VAULT=file` | Keep AI providers' keys in the owner-only octos profile instead of the macOS keychain. | +| `OCTOS_APP_CORE_BIN`, `OCTOS_APP_CORE_DIR` | The octos kernel binary the shell's kernel service runs (none: no kernel on this desktop) and its core dir (default `~/octos-home/.octos`; the AI providers profile is `/profiles/_main.json`). | +| `MAKEPAD_REMOTE`, `MAKEPAD_HIDE_WINDOWS` | Remote-control bridge; hidden windows (see [Demos](#demos)). | -## Add an app +## The app model -The default [config/apps.json](config/apps.json) includes Reference and Makepad's own apps. Plain `cargo run` uses this catalog. An additional copy is available for explicit selection: +The launcher lists four kinds of app together: -```sh -cargo run -- --apps config/apps.makepad.json -``` +| Kind | Comes from | Runs as | Launcher id | +| --- | --- | --- | --- | +| **System apps**: News, Photos, Maps, Camera, Mail, AI providers | OctoSense-System-Apps `apps//bundle`, selected by `system-apps.json`, packed into the build | Contained Splash programs in App Hub's Card runner, each in its own isolate under the capabilities its manifest asks for | `` (manifest id `os.`) | +| **Store apps** | The signed App Hub catalog, installed from the store (`apphub`) | The same Card runner. Every open is checked against the catalog; an update closes old instances. | `hub:` | +| **Native modules** | Rust crates linked into this binary | In-process `AppModule`s. Trusted code only: App Hub, AppCard, Reference and the `app-*` features. | module id | +| **Developer programs** | `config/apps.json` | Separate processes in tiles, over Makepad's `--stdin-loop` hosting protocol, built on first launch | catalog `id` | + +Precedence: a linked native module beats a system app of the same id, and a system app beats a catalog row of the same id. That is why Makepad's example Mail and Photos are dropped from the shipped catalogs (`drop` in `config/apps.overlay.json`). + +### Containment and permissions -It includes Reference plus Makepad's Browser, Files, Terminal, Mixer, Task Manager, Sheets, Photos, Clock, Weather, Finance, Mail, Notes, Calendar, Reminders, Calculator, Fabric, Score, Video Player, Route, VJ, Fab and Director. Image/PDF viewers are registered for file-opening and previews, and AI is registered for the assistant pane (F10). These three helper apps also appear in the launcher unless their IDs (`image`, `pdf`, `aichat`) are listed in `~/.octosense/wm/launcher.hides`. +A contained app is a bundle: `manifest.json` (id, version, capabilities) plus `main.splash`. The Card runner grants only the capabilities the manifest lists (Mail asks for `storage` and `mail`). The pinned Makepad ([makepad#30](https://github.com/OctoSense-org/makepad/pull/30)) enforces this at every exit of an isolate: network and web sockets answer to the app's host list, raw sockets and servers are refused, files stay in the app's storage jail, and password or one-time-code fields are inert inside a policed isolate. -Makepad's apps carry `"source": "makepad"` instead of a path: they resolve through Cargo's dependency graph to the shared runtime checkout prepared above, or to Cargo's cached checkout when Git dependencies are used without path overrides. Each app builds on demand using its package's normal default features. Those builds go to `~/.octosense/build/makepad` rather than into Cargo's cache, which Cargo alone manages. The catalog uses the workspace root manifest to preserve the apps' expected working directory. Files retains the catalog's `--demo` argument; remove it to browse your real filesystem. Fab uses its built-in demo unless you add explicit file arguments. Upstream replaced Studio with Director; the catalog keeps the `studio` ID for existing launch references and runs `makepad-director`. No apps start automatically; `--assistant` remains opt-in. +### Host services and host-owned sheets -Hosted apps and the host therefore always share one revision's framework and protocol code. Reference builds from this repository and is available regardless. A personal `~/.octosense/apps.json` takes precedence over the project default, while `--apps` always selects the named file. Relative manifest paths are based on the catalog's directory, so use absolute paths if moving this catalog into your home directory. +Secrets are the host's. An app that needs an account calls a **host service** through `host.request`; the service runs in the shell with the credentials, and the app never gets a socket or a password. -Applications must be compatible Makepad applications that support the `--stdin-loop` hosting protocol. Use the same Makepad revision as this project; the protocol is not a stable compatibility boundary across arbitrary revisions. Start from [apps/reference](apps/reference). +Mail is the worked example (`octosense-mail-service`, from `../OctoSense-System-Apps/apps/mail/host-service`): -The default catalog is [config/apps.json](config/apps.json). To keep a personal catalog, create `~/.octosense/apps.json`; it replaces the default catalog. An explicit catalog can be selected with: +- `mail.add_account` raises the host's **sign-in sheet**, a separate isolate drawn over the app. Only that sheet's calls (`mail.sheet.submit`, `mail.sheet.cancel`) can carry a password. +- The service tests the account, stores the password in the platform secret store (macOS keychain), and grants the account only to the app that added it. +- Mail state lives under the host's own directory, outside every app's jail. + +AI providers (`os.ai-providers`) edits the octos kernel's LLM providers through the `llm` service (`octosense-llm-service`, from `../OctoSense-System-Apps/apps/ai-providers/host-service`). Keys are typed only on host sheets and go to the macOS keychain entry octos reads; the providers are written to the kernel's profile under the shell's octos core dir (`/profiles/_main.json`; core dir `OCTOS_APP_CORE_DIR`, else `~/octos-home/.octos`). A phone's provider QR is imported from a picture of it: **Choose image** opens the open panel, or drop a screenshot on the import sheet. **Start → Settings → AI providers** opens it. After a change the service restarts the kernel if one runs; its consumers (AppCard) reconnect to the new one. + +**The octos kernel** is a shell service, not part of any app: `octosense-octos-core` (feature `octos-core`, default). The shell configures it at startup (`apps::configure_octos_kernel`); nothing runs until a consumer connects, then one kernel per process (` serve --stdio --data-dir ` on a desktop, the APK's `liboctos.so` on Android; none on iOS or on a desktop without `OCTOS_APP_CORE_BIN`). AppCard's agent connects to it; Rinx's host can take its own connection. It stops when the last consumer leaves and when the shell exits. + +New app features that need a password, PIN or token belong in a host service and a host sheet, never in the app's own UI. + +### Store apps (App Hub) + +App Hub is on by default. Open **App Hub** from the launcher to browse the signed catalog and install apps; installed apps appear in the launcher without a restart. The catalog origin defaults to the App Hub repository and can be pointed elsewhere with `OCTOSENSE_HUB`. To build and publish an app, start from [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow). + +#### Try your own app before it is published + +Publish the bundle into a local catalog with a throwaway anchor (OctoScript-App-Design-Flow's [PUBLISHING §4](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/PUBLISHING.md#4-rehearse-the-store-path-locally) gives the `hub keygen`/`certify`/`publish` commands), then point this shell at it: ```sh -cargo run -- --apps /path/to/apps.json +OCTOSENSE_HUB= OCTOSENSE_HUB_ANCHOR= \ + OCTOSENSE_HOME=/tmp/octosense-test OCTOSENSE_APP_DATA=/tmp/octosense-test-apps \ + cargo run --release ``` -A catalog is a JSON array. Each entry chooses exactly one of a Cargo manifest, an installed executable, or a named upstream source: +Open **App Hub**, choose the app, **Get**, scroll to **Install**, then **Open**: it runs in the Card runner under its manifest, as a store app would. Verified on macOS on 2026-09-26 with a new script app. The two `OCTOSENSE_*` state variables keep the test out of `~/.octosense`. + +### Choosing and overriding system apps + +`system-apps.json` names the apps and where their bundles are: + +```json +{ + "schema": 1, + "source": "../OctoSense-System-Apps/apps", + "apps": ["news", "photos", "maps", "camera", "mail", "ai-providers"], + "assets": {} +} +``` + +- Remove an id from `apps` to leave it out; point `OCTOSENSE_SYSTEM_APPS` at another file for a different selection. Without that variable the build ships no system apps. +- To try a changed bundle, edit it in the `../OctoSense-System-Apps` checkout and rebuild; to ship it, land it in OctoSense-System-Apps and bump `native-apps.lock.json`. +- The desktop mounts no photo library, so Photos shows the thumbnails its bundle ships. To give it full-size photos, add `"assets": {"photos": {"photos": ""}}`. +- A native module of the same id overrides a system app (for example `--features app-photos`). + +### Developer programs and the catalog + +`config/apps.json` lists Reference and Makepad's own apps (Browser, Files, Terminal, Sheets, Notes, Calendar, Director under the id `studio`, and more). The Image, PDF and AI helpers also appear in the launcher unless their ids (`image`, `pdf`, `aichat`) are listed in `wm/launcher.hides` under the state directory. + +Catalog lookup: `--apps ` if given, else `~/.octosense/apps.json` if it exists, else `config/apps.json`. A catalog is a JSON array; each entry picks one launch target: ```json [ - { - "id": "notes", - "label": "Notes", - "manifest": "../notes/Cargo.toml", - "package": "my-notes", - "bin": "notes", - "policy": "new", - "args": [] - }, - { - "id": "installed-notes", - "label": "Installed Notes", - "executable": "/opt/my-apps/notes", - "policy": "focus" - }, - { - "id": "browser", - "label": "Browser", - "source": "makepad", - "package": "makepad-browser", - "bin": "browser", - "policy": "focus" - } + { "id": "notes", "label": "Notes", "manifest": "../notes/Cargo.toml", "package": "my-notes", "bin": "notes", "policy": "new", "args": [] }, + { "id": "installed-notes", "label": "Installed Notes", "executable": "/opt/my-apps/notes" }, + { "id": "browser", "label": "Browser", "source": "makepad", "package": "makepad-browser", "bin": "browser", "policy": "focus" } ] ``` -`"source": "makepad"` names the host's Makepad dependency rather than a location: the row resolves through the checkout reported by Cargo, and is skipped on a host that has no such checkout. Relative paths resolve from the catalog's directory. Arguments are passed literally, without a shell. `policy: "new"` opens a new instance; `"focus"` focuses an existing instance and is the default. Restart OctoSense after editing the catalog. Existing personal catalogs should rename `makeos-reference` package/bin entries to `octosense-reference`. Only apps with available launch targets appear in the launcher. Missing manifests, invalid catalogs, and failed starts are reported in the desktop/logs. +- `"source": "makepad"` resolves through Cargo to the same Makepad checkout as the host; such builds go to `~/.octosense/build/makepad`. +- Step-by-step guides (Chinese): [open a hosted app](docs/open-apps.md), including the fix for an empty catalog when `cargo metadata --offline` fails, and [open the full Makepad catalog](docs/add-all-makepad-apps.md). +- Relative paths resolve from the catalog's directory. Arguments are passed literally, without a shell. +- `policy`: `"new"` opens another instance; `"focus"` (default) focuses a running one. +- Do not add `--stdin-loop` or Studio variables; the shell adds them. Restart after editing. +- A hosted program must be a Makepad app built against the same Makepad revision; the hosting protocol is not stable across revisions. Start from `apps/reference`. -The Makepad rows in the shipped catalog are generated from upstream's own registry at the pinned revision, under the named adaptations in [config/apps.overlay.json](config/apps.overlay.json). Check for drift, or regenerate, with: +The Makepad rows are generated from upstream's app registry at the pinned revision: ```sh -python3 scripts/upstream.py catalog -python3 scripts/upstream.py catalog --apply +python3 scripts/upstream.py catalog # report drift +python3 scripts/upstream.py catalog --apply # rewrite config/apps.json and apps.makepad.json ``` -OctoSense adds the hosting arguments and connection settings itself. Do not add `--stdin-loop` or Studio connection variables to the catalog. For executable registrations, supply the app's resources as required by that app's packaging. +### The AppCard assistant -## Settings and optional features +AppCard is **not shipped for now**: it interfered with the other apps, so no build links it unless asked. Default, `mobile-apps`, Android and iOS builds leave its UI out (the octos kernel service stays), and it has no tile, group or launcher entry. `--features app-appcard` brings it back on any target (for a phone, pass the feature to `cargo makepad`); it needs the `../OctoSense-System-Apps` sibling that `tools/setup-native.py` prepares and pulls octos at the revision below. -OctoSense state lives under `~/.octosense`; `OCTOSENSE_HOME` selects another directory. Existing installations continue using `~/.makeos` if `~/.octosense` does not exist, preserving settings and local model links. The old `MAKEOS_HOME` override remains supported; `OCTOSENSE_HOME` takes precedence. Theme files and hosting overrides retain the upstream `wm/` layout inside that directory. Hosted apps inherit the OctoSense state root through Makepad's compatible `MAKEPAD_HOME` setting. +`apps/appcard` (feature `app-appcard`, opt-in) hosts the whole AppCard assistant in one tile: `octos-app` from `../OctoSense-System-Apps/apps/appcard/app/app`, built without its `standalone` feature. Routing, cards, sessions, the composer and the kernel agent run inside the tile's isolate; `ask` is the module's AI-bus tool. -Optional launch flags are `--assistant`, `--prewarm`, `--demo-home`, and `--download-wallpapers`. Assistant/prewarm flags require matching apps in your catalog. Theme importing remains an explicit action in the theme menu. +```sh +cargo run --release --features app-appcard -- --module appcard +``` -Upstream's linked-module infrastructure is retained behind `app-sheets`, `app-photos`, and `app-aichat` Cargo features; all are off by default. A module must be linked and selected with `--module ` or `wm/apps.splash`. This initial milestone validates process hosting. It does not provide runtime loading of native shared libraries or embedding of unrelated native desktop windows. +It starts no kernel of its own: it connects to the shell's. On desktop that needs `OCTOS_APP_CORE_BIN` (and optionally `OCTOS_APP_CORE_DIR`); without a kernel it shows its login / WebSocket screen. Every octos crate comes from octos-org/octos at the one revision `octos-app` and `crates/octos-core` pin (`6ad76e5c`). -## Local AI setup +## Demos -Store model weights outside this repository, normally at `~/.octosense/weights/Qwen3.5-9B-UD-Q4_K_XL.gguf`. Each user downloads the model once or references an existing copy; model files and machine-specific symlinks stay out of Git. `OCTOSENSE_HOME` relocates the state directory, and `MAKEPAD_AI_CHAT_MODEL` can select a model file anywhere on disk. +### Mail without an account -The [local AI setup guide](docs/local-ai.md) covers the assistant app prerequisite, the pinned model download and checksum, reusing existing weights, and checking the **F10** assistant. The desktop and Reference app work without a model. +```sh +MAKEPAD_APP_CONFIG='{"mail_demo":true}' cargo run --release +``` -## Upstream updates +Open **Mail**, sign in on the host sheet with any address and password `demo`. The demo serves sample messages from a file vault: no network, no keychain. -[upstream/makepad.json](upstream/makepad.json) records every imported WM file, its original path/hash, and the matching framework revision. The source and dependency baseline is [official Makepad at 74b63be8](https://github.com/makepad/makepad/commit/74b63be83e101ab3a28d3604df77e9662d50a833). All framework crates, including `libs/wm_api` and `libs/wm_theme`, stay external at that exact Git revision. OctoSense owns its additional style, theme assets and wallpaper behavior locally. +With a real account on an unsigned development build, macOS asks for keychain access again after every rebuild. For development, keep passwords in a file instead: -Run this daily, or after any upstream pull. With Python 3.11+, update the Makepad -checkout using your normal Git workflow, then run one command from OctoSense: +```sh +OCTOSENSE_MAIL_VAULT=file cargo run --release +``` + +### Remote-control bridge + +Every desktop Makepad app, this shell included, carries a localhost HTTP control surface. Start it with `MAKEPAD_REMOTE=`, `MAKEPAD_REMOTE=on` (ephemeral port; a number is always read as the port, so `1` means port 1 and fails), or `--remote[=PORT]`: ```sh -git -C ../makepad pull --ff-only origin work -python3 scripts/upstream.py sync +MAKEPAD_REMOTE=8399 cargo run --release +# prints: [makepad-remote] listening on 127.0.0.1:8399 pid=... app=... grabs=... ``` -`sync` defaults to the recorded `../makepad` checkout's current `HEAD`. -WM feature development belongs in this repository; framework updates come from -official Makepad. When the checkout's HEAD matches the -recorded revision, it exits without building. Otherwise it requires a clean -OctoSense tree, saves comparison diffs, stages the merge, updates all dependency -pins and the lockfile, runs compile/Rust/Python checks, builds both profiles, -and runs both native smoke modes. It reuses an ignored staging build cache. +| Route | Does | +| --- | --- | +| `/` | Cheat sheet of every route. | +| `/s` | Windows and their geometry. | +| `/snap?q=` | Visible widgets with rects and text, filtered by id/type/text. | +| `/click?x=&y=` | Click at window-local layout points. `/m`, `/k`, `/t` for mouse, keys, text. | +| `/g` | Grab a window to PNG. | +| `/log?n=` | Tail the log. | +| `/gq` | Grab every window, then quit. Use this (or `/quit`) to end any session you started. | -After every check passes, it creates a unique `sync/makepad-` branch -and applies the verified changes, leaving them unstaged and uncommitted. You -then review the diff and report, commit, and merge. Conflicts or failed checks -stop with diagnostics and preserve the live import. The command never pulls, -commits, merges branches, or pushes. Full sync currently requires macOS GUI -access; a headless session cannot pass its native runtime checks. +Add `&wait=1` to an input route to answer after the next frame. The bridge injects real input and serves screenshots: bind a non-loopback host (`MAKEPAD_REMOTE=0.0.0.0:8399`) only on a trusted network. -Reports and captured frames are under `target/makepad-sync/reports/`; the command -prints the exact directory. Use `--source /path/to/makepad` or `--to ` -when needed. See the [full workflow and conflict recovery](docs/upstream.md) for -manual commands and how to investigate a failed candidate. +### Headless UI checks -## Verification and scope +On macOS, `MAKEPAD_HIDE_WINDOWS=1` keeps windows off screen while still rendering, so a remote-driven run does not take over the display: + +```sh +MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=on cargo run --release +``` + +Makepad's [`makepad_test`](https://github.com/OctoSense-org/makepad/tree/main/libs/makepad_test) crate (in the `../makepad` sibling) builds on the same two pieces: `#[makepad_test]` tests launch the app hidden, drive it over `--remote` with selectors and waits, and close it with `/gq`. This repository does not have a `makepad_test` suite yet. + +## Phones + +With the Makepad Android toolchain installed and a device on ADB: + +```sh +cargo makepad android run -p octosense --release +``` + +Phone builds always link Reference and Sheets and the octos kernel service, and App Hub with the system apps through the default feature; AppCard only with `--features app-appcard`. The launcher label is **OctoSense**, application id `dev.makepad.octosense`. The APK must bundle the kernel as `liboctos.so`: `python3 tools/android-kernel.py --sdk -- cargo makepad android run -p octosense --release` cross-builds `octos` at the revision `Cargo.lock` pins (in `target/octos-kernel/`) and runs the packager with `MAKEPAD_ANDROID_EXTRA_LIBS=liboctos.so=` (`--kernel ` for a prebuilt one). Without it the phone runs no kernel; the AI providers are still saved. AppCard's Java features (GPS, notifications, share, intents) need the fork's buildtool; see [docs/android-appcard-build.md](docs/android-appcard-build.md). The dedicated phone shell is OctoSense-ROM's `home/`. + +## Desktop styles and settings + +- Eight desktop styles. Desktop builds start in **OctoSense**, with Liquid Glass frames and a **Light / Dark** switch in the top bar; the others are Omarchy, macOS, Windows, Windows 2000, NeXTSTEP, iOS and Android. Theme sources are in `resources/themes/`, wallpaper provenance in [resources/wallpapers/README.md](resources/wallpapers/README.md). +- Keys: **⌘Space** menu, **⌘W** close tile, **⌘F** tile fullscreen, **⌘1…0** workspaces, **⌘Shift1…0** move tile. **Learn → Keybindings** lists them; see [KEYBINDINGS.md](KEYBINDINGS.md). +- State lives in `~/.octosense` (`OCTOSENSE_HOME`); hosted apps get it as `MAKEPAD_HOME`. +- Local models for the AI pane (**F10**): [docs/local-ai.md](docs/local-ai.md). The desktop works without a model. + +## Pinning and updating siblings + +| To move | Edit | Then | +| --- | --- | --- | +| Makepad / OctoScript | `native-runtime.lock.json` (a new OctoScript-Makepad release), and the `rev` of the Makepad Git dependencies in `Cargo.toml` and `apps/*/Cargo.toml` | If a patch is needed on top, record it in `runtime-patches.lock.json` (base, sha256, tree) | +| System apps, Mail service, AppCard | `revision` in `native-apps.lock.json` | — | +| App Hub | `rev` of `octosense-app-hub-app` in `Cargo.toml` | Keep it at the App Hub rev the pinned System-Apps Mail and `llm` services name (`octosense-appstore`), so the graph has one App Hub | + +After any change: `python3 tools/setup-native.py --update`, `cargo update` as needed, then `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` and the tests below. + +`scripts/upstream.py sync|status|diff|update` tracks the WM files imported from official Makepad (`upstream/makepad.json`, baseline `74b63be8`); see [docs/upstream.md](docs/upstream.md). It needs `--source` pointing at a full clone of official Makepad: the `../makepad` sibling is a shallow checkout of the fork and does not contain the baseline commit. + +## Testing ```sh cargo test --locked --workspace +cargo test --locked --workspace --features mobile-apps +cargo test --locked -p octosense --features mobile-apps,app-appcard appcard +cargo tree --locked --features mobile-apps -i octosense-octos-core # the octos kernel service is linked +cargo tree --locked --features mobile-apps -i octosense-appcard # must not match: no AppCard UI without app-appcard +python3 -m unittest tools/test_android_kernel.py python3 -m unittest discover -s scripts -p 'test_*.py' +python3 scripts/upstream.py catalog +python3 tools/setup-native.py --check --cargo-manifest Cargo.toml ``` -The native smoke test opens and closes its own test windows, isolates settings in a temporary directory, and records app-provided frames/logs. After dependencies are downloaded: +Native smoke tests open their own windows, isolate state in a temporary directory and drive the shell over the remote bridge. They need GUI access and a prior release build: ```sh cargo build --release --locked --workspace @@ -248,8 +348,25 @@ python3 scripts/smoke.py --styles python3 scripts/smoke.py --cargo-run --default-catalog ``` -The first smoke command checks hosted input, workspace movement, fullscreen resizing, independent instances, all eight desktop styles (including both OctoSense appearances, glass and menus), failed launches, and quitting during an unfinished build. The second uses exactly `cargo run` with the shipped catalog. Python supplies app-local remote control and isolated state through the environment; neither is required for normal use. Smoke runs set Cargo offline and require GUI access. +Results for each change are recorded in [docs/validation.md](docs/validation.md). + +## CI + +`.github/workflows/runtime.yml` runs on every push and pull request, on macOS 14: `setup-native.py`, `cargo check --locked` (default and `--features mobile-apps`), `cargo tree` checks that the octos kernel service is in the default and `mobile-apps` graphs and the AppCard UI (`octosense-appcard`) is not (host and Android), `cargo check --locked --workspace --features mobile-apps,app-appcard`, the `tools/android-kernel.py` tests, and `setup-native.py --check --cargo-manifest Cargo.toml`. It does **not** run `cargo test`, the other Python tests or the smoke tests; run those locally before opening a PR. + +## Known gaps + +- Only macOS is validated. Windows and Linux are untested; the iOS build fails in the pinned Metal backend. +- No packaged `.app` or installer; source builds read fonts and resources from the `../makepad` checkout, so keep it in place. +- Photos on desktop has thumbnails only unless you mount a photo directory. +- The hosted AppCard assistant does not yet wire notifications, share or the WebView overlay. +- Mobile Sheets needs grid-label and toolbar fixes ([BACKLOG.md](BACKLOG.md)). +- No `makepad_test` UI suite; CI compiles but does not test. + +## Contributing + +`main` is protected: every change goes through a pull request (admins included), and force pushes are blocked. Branch from `main`, run `setup-native.py --check`, the Rust and Python tests above and, for UI changes, a smoke or remote-driven run; record native checks in `docs/validation.md`. Keep the one-Makepad, one-octos, one-App-Hub rule: `setup-native.py --check --cargo-manifest Cargo.toml` must pass. -See the [validation record](docs/validation.md). Source builds and process hosting are the initial target on macOS. Linux/Windows branches are retained but have not been validated here. A relocatable `.app`, installer, web/mobile delivery, and a Linux session compositor are separate work. +## License -OctoSense is licensed under the [Apache License 2.0](LICENSE); see [NOTICE](NOTICE). The copied Makepad source is covered by its [original MIT notice](LICENSES/Makepad-MIT.txt). Dependencies retain their respective licenses. +Apache License 2.0 ([LICENSE](LICENSE), [NOTICE](NOTICE)). Source copied from Makepad keeps its [MIT notice](LICENSES/Makepad-MIT.txt). Dependencies keep their own licenses. diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 00000000..17508c9a --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,371 @@ +# OctoSense Desktop + +[English](README.md) | 简体中文 + +OctoSense-Desktop 是 [OctoSense](https://github.com/OctoSense-org)(运行在操作系统之上的 Agent 交互 Shell)的桌面端 Shell。它是一个 Makepad 窗口,这个窗口本身就是桌面:launcher、dock 和平铺窗格(tile)。系统应用和 App Hub 商店应用以隔离的脚本程序运行,受信任的原生模块在进程内运行,Makepad 开发者程序作为子进程运行。它获取应用的方式与手机 Shell(OctoSense-ROM 的 `home/`)完全相同。 + +**要开发 OctoSense 应用?** 构建、检查和发布应用都不需要本仓库:请从 [OctoSense-org 主页](https://github.com/OctoSense-org)的阅读列表开始(先读 OctoScript-App-Design-Flow 的 `AGENTS.md`,再读 `docs/QUICKSTART.md`)。只有想在发布前在桌面 Shell 中看到自己的应用时,才需要构建本 Shell(见[发布前试用自己的应用](#发布前试用自己的应用))。 + +## 在仓库体系中的位置 + +| 仓库 | 与本仓库的关系 | +| --- | --- | +| [OctoSense-ROM](https://github.com/OctoSense-org/OctoSense-ROM/blob/main/README.zh-CN.md) | 手机 Shell(`home/`)。相同的应用模型、相同的运行时、相同的系统应用。 | +| [OctoSense-System-Apps](https://github.com/OctoSense-org/OctoSense-System-Apps) | 新闻、相册、地图、相机、邮件、AI 提供商的应用包,邮件与 `llm` 宿主服务,octos 内核服务(`crates/octos-core`),以及 AppCard 助手(`octos-app`,需显式启用,默认不发布)。以固定版本的同级目录检出。 | +| [OctoSense-App-Hub](https://github.com/OctoSense-org/OctoSense-App-Hub) | 签名目录、商店和 Card 运行器。以 Git crate `octosense-app-hub-app` 链接。 | +| [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) | 设计、构建应用并发布到 App Hub 的地方。 | +| [OctoScript-Makepad](https://github.com/OctoSense-org/OctoScript-Makepad) | 固定 Makepad 与 OctoScript 版本的运行时发行版。以固定版本的同级目录检出。 | +| [makepad(OctoSense 分支)](https://github.com/OctoSense-org/makepad) | 框架。以固定版本的同级目录检出。 | +| [octos](https://github.com/octos-org/octos) | Agent 内核,一项 Shell 服务(`octos-core`,默认开启):由 AI 提供商配置,AppCard 等使用方连接它。只有一个版本(`6ad76e5c`,即 System-Apps 固定的版本),记录在 `Cargo.lock` 中;内核本身是单独的二进制(桌面:`OCTOS_APP_CORE_BIN`;Android:打包的 `liboctos.so`)。 | + +## 仓库结构 + +| 路径 | 内容 | +| --- | --- | +| `src/` | Shell 本体(crate `octosense`):平铺、launcher、dock、状态栏、应用托管(`host.rs`、`hub.rs`、`module_host.rs`)、应用注册表(`apps.rs`)。 | +| `src/shell/` | 状态栏、launcher、菜单、通知、AI 面板、gallery。 | +| `src/octosense/` | OctoSense 专有部分:应用目录加载、状态路径、样式、Makepad 源码定位。 | +| `apps/reference/` | `octosense-reference`,一个计数器加文本输入的小应用,既能作为托管进程运行,也能作为链接模块运行。 | +| `apps/appcard/` | `octosense-appcard`,在一个 tile 中挂载 AppCard 助手(`octos-app`)的模块。需显式启用(`app-appcard`);目前不随产品发布。 | +| `config/apps.json` | 默认的开发者程序目录。`apps.makepad.json` 是供 `--apps` 使用的相同副本;`apps.overlay.json` 保存重新生成时应用的调整。 | +| `system-apps.json` | 本次构建打包哪些系统应用,以及从哪里取。 | +| `native-runtime.lock.json` | OctoScript-Makepad 发行版(并由它确定 Makepad 和 OctoScript)。 | +| `native-apps.lock.json` | OctoSense-System-Apps 的版本。 | +| `runtime-patches.lock.json` | 固定 Makepad 之上经过评审的补丁及其期望的源码树。目前为空:固定的 Makepad 已包含 Shell 所需的一切。 | +| `tools/setup-native.py` | 准备并检查固定版本的同级目录。 | +| `scripts/` | `upstream.py`(WM 来源记录与目录重新生成)、`smoke.py`(原生冒烟测试)、它们的 Python 测试、`system_apps_remote.sh` 和 `ai_providers_remote.sh`(以隐藏窗口 `--remote` 运行的系统应用与 AI 提供商端到端测试),以及 `provision-appcard-llm.sh`(Android)。 | +| `upstream/makepad.json` | 每个从 Makepad `apps/wm` 引入的文件的来源记录。 | +| `resources/` | 主题、壁纸、图标、Android manifest 模板、启动脚本。 | +| `docs/` | [验证记录](docs/validation.md)、[上游同步](docs/upstream.md)、[本地 AI](docs/local-ai.md)、[Android AppCard 构建](docs/android-appcard-build.md)、按日期的计划文档。 | +| `KEYBINDINGS.md`、`BACKLOG.md` | 快捷键说明;待办事项。 | + +## 前置条件 + +- 稳定版 Rust(`cargo` 位于 `~/.cargo/bin`)以及所在系统的原生工具链。macOS 需要 Xcode Command Line Tools(`xcode-select --install`)。 +- Git,以及供 `tools/setup-native.py` 使用的 Python 3.9+。`scripts/upstream.py` 需要 Python 3.11+(它导入 `tomllib`)。 +- 首次准备和构建时需要联网。 + +## 准备同级工作区 + +构建时,Makepad、OctoScript、OctoScript-Makepad 和 OctoSense-System-Apps 都作为本仓库的**同级目录**解析(`../makepad` 等,通过 `Cargo.toml` 中的 `[patch]` 条目)。把本仓库放进一个单独的工作区目录,再由准备脚本补齐其余部分: + +```sh +mkdir octosense-ws && cd octosense-ws +git clone https://github.com/OctoSense-org/OctoSense-Desktop.git +cd OctoSense-Desktop +python3 tools/setup-native.py +``` + +结果: + +```text +octosense-ws/ + OctoSense-Desktop/ this repository + octoscript-makepad/ the release native-runtime.lock.json selects + makepad/, octoscript/ the revisions that release's runtime.json pins + OctoSense-System-Apps/ the revision native-apps.lock.json pins +``` + +| 命令 | 作用 | +| --- | --- | +| `python3 tools/setup-native.py` | 按固定版本克隆缺失的同级仓库(并应用 `runtime-patches.lock.json` 中列出的补丁,如有)。 | +| `python3 tools/setup-native.py --check` | 只校验同级目录,不做任何修改。 | +| `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` | 同时检查锁定的 Cargo 依赖图:只有一个 Makepad、一个 octos、一个 App Hub。 | +| `python3 tools/setup-native.py --update` | 把干净的检出移动到锁定版本(在锁文件变更之后)。 | +| `--root DIR`、`--cache DIR` | 使用其他工作区目录;复用本地 Git 对象缓存。 | + +同级目录中的本地修改会被保留;`--update` 只移动干净的检出。当 `runtime-patches.lock.json` 列出补丁时,该补丁所基于的提交(`source_commit`)的干净检出,会被视为同一棵源码树。 + +## 构建与运行 + +准备完成后,在本目录执行: + +```sh +cargo run --release +``` + +桌面启动时是空的。从 dock、左上角的 **Apps** 菜单或 **⌘Space**(菜单和搜索)启动 App Hub、系统应用或开发者程序。**System → Quit OctoSense** 关闭桌面及其托管的所有内容。 + +`config/apps.json` 中的开发者程序在首次启动时构建(进度显示在 tile 中)。如需预先构建整个工作区: + +```sh +cargo build --release --workspace +``` + +| 平台 | 状态 | +| --- | --- | +| macOS | 已支持并验证(源码构建、进程托管、App Hub、系统应用)。 | +| Windows、Linux | 保留了上游的代码路径,但在本仓库未经验证。 | +| Android | `cargo makepad android run -p octosense --release`;见[手机](#手机)。 | +| iOS | 启动策略有测试覆盖,但完整构建目前在固定版本的 Makepad Metal 后端中失败([验证记录](docs/validation.md))。 | + +目前不提供可重定位的 `.app`、安装包,也没有 Linux 会话合成器。 + +### Cargo features + +| Feature | 默认 | 作用 | +| --- | --- | --- | +| `app-hub` | 开 | 链接 `octosense-app-hub-app`(商店 `apphub`、Card 运行器 `card`、系统应用)以及宿主服务 `octosense-mail-service`(邮件)和 `octosense-llm-service`(AI 提供商)。关闭后构建中没有 App Hub,也没有系统应用。 | +| `app-reference` | 关 | 把 Reference 作为模块链接。 | +| `app-sheets` | 关 | 把 Makepad 的 Sheets 作为模块链接。 | +| `app-photos` | 关 | 链接 Makepad 的原生 Photos 模块;它会替换同 id 的相册系统应用(用于对比)。 | +| `app-appcard` | 关 | 链接 AppCard 助手模块(`apps/appcard`);隐含 `octos-core`。在所有目标平台(包括手机)上都需显式启用;目前不随产品发布。 | +| `octos-core` | 开 | octos 内核服务(`octosense-octos-core`,来自 `../OctoSense-System-Apps/crates/octos-core`):AppCard 等使用方共享的唯一内核,由 AI 提供商配置。Android 和 iOS 上总是开启。用 `--no-default-features --features app-hub`(加上其他需要的 feature)去掉它。 | +| `app-aichat` | 关 | 把 Makepad 的 AI chat 作为模块链接,不含模型引擎。 | +| `app-rinx` | 关 | 把 Matrix 客户端 [Rinx](https://github.com/upstreamlabs/Rinx) 作为模块链接。 | +| `mobile-apps` | 关 | `app-reference` + `app-sheets` + `app-hub` + `octos-core`:手机构建链接的同一组模块,用于在桌面上测试。不含 AppCard。 | + +已链接的模块用 `--module ` 打开(或在状态目录下的 `wm/apps.splash` 中写一行 `: Module`): + +```sh +cargo run --release --features app-appcard -- --module appcard +cargo run --release --features mobile-apps -- --module reference --module sheets +cargo run --release --features app-rinx -- --module rinx +``` + +App Hub 的模块是例外:它们没有进程形态,总是在进程内打开。 + +### 命令行参数与环境变量 + +| 名称 | 作用 | +| --- | --- | +| `--apps ` | 使用指定的开发者程序目录。 | +| `--module ` | 在进程内托管一个已链接的模块。 | +| `--assistant`、`--prewarm` | 启动助手应用 / 预热应用(需要目录中有对应条目)。默认关闭。 | +| `--demo-home`、`--download-wallpapers` | 生成演示文件系统;下载 Omarchy 主题的完整壁纸集。 | +| `OCTOSENSE_HOME` | 状态目录(默认 `~/.octosense`;若不存在则沿用已有的 `~/.makeos`,以及 `MAKEOS_HOME`)。 | +| `OCTOSENSE_APP_DATA` | App Hub 保存已安装应用的位置(默认是平台数据目录下的 `apps/`)。 | +| `OCTOSENSE_HUB`、`OCTOSENSE_HUB_ANCHOR` | App Hub 目录来源(路径或 URL)和信任锚;默认是 App Hub 仓库的 `main`。 | +| `OCTOSENSE_SYSTEM_APPS` | 系统应用选择文件;`.cargo/config.toml` 将其设为 `system-apps.json`。 | +| `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` | 提供邮件的演示邮箱(见[演示](#演示))。 | +| `OCTOSENSE_MAIL_VAULT=file` | 把邮件密码保存在权限为 0600 的文件中,而不是 macOS 钥匙串。 | +| `OCTOSENSE_LLM_VAULT=file` | 把 AI 提供商的密钥保存在仅所有者可读的 octos 配置文件中,而不是 macOS 钥匙串。 | +| `OCTOS_APP_CORE_BIN`、`OCTOS_APP_CORE_DIR` | Shell 的内核服务运行的 octos 内核二进制(未设置则本机没有内核)及其 core 目录(默认 `~/octos-home/.octos`;AI 提供商的 profile 是 `<目录>/profiles/_main.json`)。 | +| `MAKEPAD_REMOTE`、`MAKEPAD_HIDE_WINDOWS` | 远程控制桥;隐藏窗口(见[演示](#演示))。 | + +## 应用模型 + +launcher 把四类应用列在一起: + +| 类别 | 来源 | 运行方式 | Launcher id | +| --- | --- | --- | --- | +| **系统应用**:新闻、相册、地图、相机、邮件、AI 提供商 | OctoSense-System-Apps 的 `apps//bundle`,由 `system-apps.json` 选择,打包进构建 | App Hub 的 Card 运行器中隔离运行的 Splash 程序,每个应用一个 isolate,只拥有其清单申请的能力 | ``(清单 id `os.`) | +| **商店应用** | 签名的 App Hub 目录,从商店(`apphub`)安装 | 同一个 Card 运行器。每次打开都会对照目录检查;更新会关闭旧实例。 | `hub:` | +| **原生模块** | 链接进本二进制的 Rust crate | 进程内的 `AppModule`。只允许受信任的代码:App Hub、AppCard、Reference 以及各 `app-*` feature。 | 模块 id | +| **开发者程序** | `config/apps.json` | tile 中的独立进程,通过 Makepad 的 `--stdin-loop` 托管协议运行,首次启动时构建 | 目录 `id` | + +优先级:已链接的原生模块优先于同 id 的系统应用,系统应用优先于同 id 的目录条目。因此 Makepad 示例中的 Mail 和 Photos 已从随附目录中移除(`config/apps.overlay.json` 中的 `drop`)。 + +### 隔离与权限 + +隔离运行的应用是一个包:`manifest.json`(id、版本、能力)加上 `main.splash`。Card 运行器只授予清单中列出的能力(邮件申请 `storage` 和 `mail`)。固定的 Makepad([makepad#30](https://github.com/OctoSense-org/makepad/pull/30))在 isolate 的每个出口执行这一约束:网络请求和 web socket 受应用的主机列表约束,原始 socket 和服务端被拒绝,文件访问限制在应用的存储沙箱内,密码和一次性验证码输入框在受约束的 isolate 中不起作用。 + +### 宿主服务与宿主自有面板 + +密钥属于宿主。需要账户的应用通过 `host.request` 调用**宿主服务**;服务在 Shell 中持有凭据运行,应用永远拿不到 socket,也拿不到密码。 + +邮件是完整的示例(`octosense-mail-service`,来自 `../OctoSense-System-Apps/apps/mail/host-service`): + +- `mail.add_account` 弹出宿主的**登录面板**,这是覆盖在应用之上的独立 isolate。只有这个面板的调用(`mail.sheet.submit`、`mail.sheet.cancel`)可以携带密码。 +- 服务先测试账户,再把密码存入平台的密钥存储(macOS 钥匙串),并且只把账户授予添加它的应用。 +- 邮件状态保存在宿主自己的目录中,位于所有应用的沙箱之外。 + +AI 提供商(`os.ai-providers`)通过 `llm` 服务(`octosense-llm-service`,来自 `../OctoSense-System-Apps/apps/ai-providers/host-service`)编辑 octos 内核的 LLM 提供商。密钥只在宿主面板上输入,保存到 octos 读取的 macOS 钥匙串条目;提供商写入 Shell 的 octos core 目录下内核的 profile(`/profiles/_main.json`;core 目录为 `OCTOS_APP_CORE_DIR`,否则为 `~/octos-home/.octos`)。手机上的提供商二维码可从图片导入:**Choose image** 打开文件面板,或把截图拖到导入面板上。**开始 → 设置 → AI providers** 可打开它。更改后服务会重启正在运行的内核,使用方(AppCard)会重新连接到新内核。 + +**octos 内核**是一项 Shell 服务,不属于任何应用:`octosense-octos-core`(feature `octos-core`,默认开启)。Shell 在启动时配置它(`apps::configure_octos_kernel`);在有使用方连接之前不运行任何东西,之后每个进程只有一个内核(桌面上是 ` serve --stdio --data-dir `,Android 上是 APK 中的 `liboctos.so`;iOS 以及未设置 `OCTOS_APP_CORE_BIN` 的桌面上没有内核)。AppCard 的 Agent 连接它;Rinx 的宿主可以拿到自己的连接。最后一个使用方离开或 Shell 退出时内核停止。 + +需要密码、PIN 或令牌的新功能,应放在宿主服务和宿主自有面板中,绝不放在应用自己的界面里。 + +### 商店应用(App Hub) + +App Hub 默认开启。从 launcher 打开 **App Hub**,浏览签名目录并安装应用;安装后的应用无需重启就会出现在 launcher 中。目录来源默认是 App Hub 仓库,可以用 `OCTOSENSE_HUB` 指向其他位置。要构建和发布应用,从 [OctoScript-App-Design-Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) 开始。 + +#### 发布前试用自己的应用 + +用一次性信任锚把应用包发布到本地目录(命令见 OctoScript-App-Design-Flow 的 [PUBLISHING §4](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/main/docs/PUBLISHING.md#4-rehearse-the-store-path-locally):`hub keygen`/`certify`/`publish`),再让本 Shell 指向它: + +```sh +OCTOSENSE_HUB= OCTOSENSE_HUB_ANCHOR= \ + OCTOSENSE_HOME=/tmp/octosense-test OCTOSENSE_APP_DATA=/tmp/octosense-test-apps \ + cargo run --release +``` + +打开 **App Hub**,选中应用,点 **Get**,向下滚动到 **Install**,然后点 **Open**:它会像商店应用一样,在 Card runner 中按其 manifest 运行。已于 2026-09-26 在 macOS 上用一个新的脚本应用验证。两个 `OCTOSENSE_*` 状态变量让测试不影响 `~/.octosense`。 + +### 选择与覆盖系统应用 + +`system-apps.json` 列出应用及其应用包所在位置: + +```json +{ + "schema": 1, + "source": "../OctoSense-System-Apps/apps", + "apps": ["news", "photos", "maps", "camera", "mail", "ai-providers"], + "assets": {} +} +``` + +- 从 `apps` 中删除某个 id 即可不打包它;把 `OCTOSENSE_SYSTEM_APPS` 指向另一个文件即可换一套选择。没有这个变量时,构建不包含任何系统应用。 +- 想试用修改过的应用包,就在 `../OctoSense-System-Apps` 检出中修改并重新构建;要正式发布,把修改合入 OctoSense-System-Apps,再更新 `native-apps.lock.json`。 +- 桌面端没有挂载照片库,因此相册显示的是应用包自带的缩略图。要提供原尺寸照片,添加 `"assets": {"photos": {"photos": ""}}`。 +- 同 id 的原生模块会覆盖系统应用(例如 `--features app-photos`)。 + +### 开发者程序与目录 + +`config/apps.json` 列出 Reference 和 Makepad 自带的应用(Browser、Files、Terminal、Sheets、Notes、Calendar、id 为 `studio` 的 Director 等)。Image、PDF 和 AI 辅助应用也会出现在 launcher 中,除非它们的 id(`image`、`pdf`、`aichat`)写在状态目录下的 `wm/launcher.hides` 中。 + +目录查找顺序:给了 `--apps ` 就用它;否则若存在 `~/.octosense/apps.json` 就用它;否则用 `config/apps.json`。目录是一个 JSON 数组,每个条目选择一种启动目标: + +```json +[ + { "id": "notes", "label": "Notes", "manifest": "../notes/Cargo.toml", "package": "my-notes", "bin": "notes", "policy": "new", "args": [] }, + { "id": "installed-notes", "label": "Installed Notes", "executable": "/opt/my-apps/notes" }, + { "id": "browser", "label": "Browser", "source": "makepad", "package": "makepad-browser", "bin": "browser", "policy": "focus" } +] +``` + +- `"source": "makepad"` 通过 Cargo 解析到与宿主相同的 Makepad 检出;这类构建输出到 `~/.octosense/build/makepad`。 +- 相对路径相对于目录文件所在目录解析。参数按原样传递,不经过 shell。 +- `policy`:`"new"` 打开新实例;`"focus"`(默认)聚焦已运行的实例。 +- 不要添加 `--stdin-loop` 或 Studio 相关变量,Shell 会自己添加。修改后需重启。 +- 被托管的程序必须是基于同一 Makepad 版本构建的 Makepad 应用;托管协议在不同版本之间并不稳定。可以从 `apps/reference` 开始。 + +目录中的 Makepad 条目由固定版本上游的应用注册表生成: + +```sh +python3 scripts/upstream.py catalog # report drift +python3 scripts/upstream.py catalog --apply # rewrite config/apps.json and apps.makepad.json +``` + +### AppCard 助手 + +AppCard **目前不随产品发布**:它会干扰其他应用,因此除非显式要求,任何构建都不链接它。默认构建、`mobile-apps`、Android 和 iOS 构建都不包含它的界面(octos 内核服务仍然存在),也没有它的磁贴、分组或启动器条目。使用 `--features app-appcard` 可在任意目标平台上恢复它(手机构建请把该 feature 传给 `cargo makepad`);它需要 `tools/setup-native.py` 准备的 `../OctoSense-System-Apps` 同级目录,并按下文所述版本拉取 octos。 + +`apps/appcard`(feature `app-appcard`,需显式启用)在一个 tile 中托管完整的 AppCard 助手:来自 `../OctoSense-System-Apps/apps/appcard/app/app` 的 `octos-app`,构建时关闭其 `standalone` feature。路由、卡片、会话、输入框和内核 Agent 都在该 tile 的 isolate 中运行;`ask` 是该模块的 AI 总线工具。 + +```sh +cargo run --release --features app-appcard -- --module appcard +``` + +它不会自己启动内核,而是连接 Shell 的内核。在桌面上这需要 `OCTOS_APP_CORE_BIN`(`OCTOS_APP_CORE_DIR` 可选);没有内核时显示登录 / WebSocket 界面。所有 octos crate 都来自 octos-org/octos,且只有 `octos-app` 和 `crates/octos-core` 固定的那一个版本(`6ad76e5c`)。 + +## 演示 + +### 无账户试用邮件 + +```sh +MAKEPAD_APP_CONFIG='{"mail_demo":true}' cargo run --release +``` + +打开 **Mail**,在宿主面板上用任意地址和密码 `demo` 登录。演示模式从文件保险库提供示例邮件:不联网,不用钥匙串。 + +在未签名的开发构建上使用真实账户时,每次重新构建后 macOS 都会再次请求钥匙串访问权限。开发时可以改用文件保存密码: + +```sh +OCTOSENSE_MAIL_VAULT=file cargo run --release +``` + +### 远程控制桥 + +每个桌面端 Makepad 应用(包括本 Shell)都内置一个本机 HTTP 控制接口。用 `MAKEPAD_REMOTE=`、`MAKEPAD_REMOTE=on`(临时端口;数字一律按端口解析,`1` 即端口 1,会绑定失败)或 `--remote[=PORT]` 启用: + +```sh +MAKEPAD_REMOTE=8399 cargo run --release +# prints: [makepad-remote] listening on 127.0.0.1:8399 pid=... app=... grabs=... +``` + +| 路由 | 作用 | +| --- | --- | +| `/` | 所有路由的速查表。 | +| `/s` | 窗口及其几何信息。 | +| `/snap?q=` | 可见控件及其矩形和文本,可按 id/类型/文本过滤。 | +| `/click?x=&y=` | 在窗口内布局坐标处点击。`/m`、`/k`、`/t` 分别对应鼠标、按键、文本。 | +| `/g` | 把窗口截图为 PNG。 | +| `/log?n=` | 查看日志末尾。 | +| `/gq` | 截取所有窗口后退出。凡是自己启动的会话,都用它(或 `/quit`)结束。 | + +在输入类路由后加 `&wait=1`,会等下一帧绘制完成后再返回。该接口会注入真实输入并提供截图:只有在可信网络中才绑定非回环地址(`MAKEPAD_REMOTE=0.0.0.0:8399`)。 + +### 无界面 UI 检查 + +在 macOS 上,`MAKEPAD_HIDE_WINDOWS=1` 让窗口不显示在屏幕上但仍然渲染,这样远程驱动的运行不会占用屏幕: + +```sh +MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=on cargo run --release +``` + +Makepad 的 [`makepad_test`](https://github.com/OctoSense-org/makepad/tree/main/libs/makepad_test) crate(位于 `../makepad` 同级目录中)正是基于这两者:`#[makepad_test]` 测试以隐藏窗口启动应用,通过 `--remote` 用选择器和等待条件驱动它,最后用 `/gq` 关闭。本仓库目前还没有 `makepad_test` 测试套件。 + +## 手机 + +安装 Makepad Android 工具链并通过 ADB 连接设备后: + +```sh +cargo makepad android run -p octosense --release +``` + +手机构建总是链接 Reference、Sheets 和 octos 内核服务,并通过默认 feature 链接 App Hub 及系统应用;AppCard 仅在使用 `--features app-appcard` 时链接。launcher 显示名为 **OctoSense**,应用 id 为 `dev.makepad.octosense`。APK 必须以 `liboctos.so` 形式打包内核:`python3 tools/android-kernel.py --sdk -- cargo makepad android run -p octosense --release` 会按 `Cargo.lock` 固定的版本交叉编译 `octos`(放在 `target/octos-kernel/`),并以 `MAKEPAD_ANDROID_EXTRA_LIBS=liboctos.so=` 运行打包工具(`--kernel ` 使用预先编译好的内核)。没有它时手机上不运行内核;AI 提供商的设置仍会保存。AppCard 的 Java 功能(GPS、通知、分享、intent)需要使用分支的 buildtool;见 [docs/android-appcard-build.md](docs/android-appcard-build.md)。专门的手机 Shell 是 OctoSense-ROM 的 `home/`。 + +## 桌面样式与设置 + +- 共八种桌面样式。桌面构建默认使用 **OctoSense**,带 Liquid Glass 窗框,顶栏有 **Light / Dark** 切换;其余为 Omarchy、macOS、Windows、Windows 2000、NeXTSTEP、iOS 和 Android。主题源文件在 `resources/themes/`,壁纸来源见 [resources/wallpapers/README.md](resources/wallpapers/README.md)。 +- 快捷键:**⌘Space** 菜单,**⌘W** 关闭 tile,**⌘F** tile 全屏,**⌘1…0** 切换工作区,**⌘Shift1…0** 移动 tile。**Learn → Keybindings** 列出全部快捷键;另见 [KEYBINDINGS.md](KEYBINDINGS.md)。 +- 状态保存在 `~/.octosense`(`OCTOSENSE_HOME`);托管应用通过 `MAKEPAD_HOME` 获得该路径。 +- AI 面板(**F10**)的本地模型:[docs/local-ai.md](docs/local-ai.md)。没有模型时桌面照常工作。 + +## 固定与更新同级仓库 + +| 要更新的内容 | 修改 | 然后 | +| --- | --- | --- | +| Makepad / OctoScript | `native-runtime.lock.json`(新的 OctoScript-Makepad 发行版),以及 `Cargo.toml` 和 `apps/*/Cargo.toml` 中 Makepad Git 依赖的 `rev` | 如需在其上打补丁,记录到 `runtime-patches.lock.json`(base、sha256、tree) | +| 系统应用、邮件服务、AppCard | `native-apps.lock.json` 中的 `revision` | — | +| App Hub | `Cargo.toml` 中 `octosense-app-hub-app` 的 `rev` | 与固定的 System-Apps 中 Mail 服务引用的 App Hub 版本(`octosense-appstore`)保持一致,使依赖图中只有一份 App Hub | + +任何更改之后:执行 `python3 tools/setup-native.py --update`,按需执行 `cargo update`,再运行 `python3 tools/setup-native.py --check --cargo-manifest Cargo.toml` 和下面的测试。 + +`scripts/upstream.py sync|status|diff|update` 跟踪从官方 Makepad 引入的 WM 文件(`upstream/makepad.json`,基线 `74b63be8`);见 [docs/upstream.md](docs/upstream.md)。它需要用 `--source` 指向官方 Makepad 的完整克隆:`../makepad` 同级目录是分支的浅克隆,不包含基线提交。 + +## 测试 + +```sh +cargo test --locked --workspace +cargo test --locked --workspace --features mobile-apps +cargo test --locked -p octosense --features mobile-apps,app-appcard appcard +cargo tree --locked --features mobile-apps -i octosense-octos-core # octos 内核服务已链接 +cargo tree --locked --features mobile-apps -i octosense-appcard # 必须匹配不到:未启用 app-appcard 时没有 AppCard 界面 +python3 -m unittest tools/test_android_kernel.py +python3 -m unittest discover -s scripts -p 'test_*.py' +python3 scripts/upstream.py catalog +python3 tools/setup-native.py --check --cargo-manifest Cargo.toml +``` + +原生冒烟测试会打开自己的窗口,把状态隔离在临时目录中,并通过远程控制桥驱动 Shell。需要 GUI 访问权限,并且先完成 release 构建: + +```sh +cargo build --release --locked --workspace +python3 scripts/smoke.py --styles +python3 scripts/smoke.py --cargo-run --default-catalog +``` + +每次变更的结果记录在 [docs/validation.md](docs/validation.md)。 + +## CI + +`.github/workflows/runtime.yml` 在每次 push 和 pull request 时于 macOS 14 上运行:`setup-native.py`、`cargo check --locked`(默认以及 `--features mobile-apps`)、检查 octos 内核服务在默认和 `mobile-apps` 依赖图中且 AppCard 界面(`octosense-appcard`)不在其中的 `cargo tree`(主机和 Android)、`cargo check --locked --workspace --features mobile-apps,app-appcard`、`tools/android-kernel.py` 的测试,以及 `setup-native.py --check --cargo-manifest Cargo.toml`。它**不**运行 `cargo test`、其他 Python 测试或冒烟测试;提交 PR 前请在本地运行。 + +## 已知不足 + +- 只有 macOS 经过验证。Windows 和 Linux 未测试;iOS 构建在固定版本的 Metal 后端中失败。 +- 没有打包好的 `.app` 或安装包;源码构建从 `../makepad` 检出读取字体和资源,请保留该目录。 +- 桌面端相册只有缩略图,除非挂载照片目录。 +- 托管的 AppCard 助手尚未接入通知、分享和 WebView 浮层。 +- 手机端 Sheets 还需要修复网格标签和工具栏([BACKLOG.md](BACKLOG.md))。 +- 没有 `makepad_test` UI 测试套件;CI 只编译,不测试。 + +## 参与贡献 + +`main` 受保护:所有更改都必须通过 pull request(管理员也不例外),且禁止强制推送。从 `main` 创建分支,运行 `setup-native.py --check` 和上面的 Rust、Python 测试;涉及 UI 的更改还要做一次冒烟测试或远程驱动运行,并把原生检查记录到 `docs/validation.md`。保持“一个 Makepad、一个 octos、一个 App Hub”的规则:`setup-native.py --check --cargo-manifest Cargo.toml` 必须通过。 + +## 许可证 + +Apache License 2.0([LICENSE](LICENSE)、[NOTICE](NOTICE))。从 Makepad 复制的源码保留其 [MIT 声明](LICENSES/Makepad-MIT.txt)。各依赖保留各自的许可证。 diff --git a/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index 2afbca33..0a2bf335 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,12 +6,17 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -# The whole Octoscript-AppCard app as a library: routing brain, card store and +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +# The whole AppCard assistant as a library, from the pinned +# OctoSense-System-Apps checkout beside this repository (apps/appcard/app/app; +# native-apps.lock.json, tools/setup-native.py): routing brain, card store and # transport, L0 lowering, sessions, composer, kernel agent — mounted here as # its `AppShell` widget. No default features: the app's own entry points # (`app_main!`, the dev monitor) stay out; this module is the entry point. -octos-app = { git = "https://github.com/OctoSense-org/Octoscript-AppCard.git", rev = "a3d0230a0dadb2e6ae73fa8a518fb7ef6d2f7a18", default-features = false } +# Every octos crate comes from git octos-org/octos at the one rev octos-app +# pins; OCTOSENSE_WORKSPACE (.cargo/config.toml) points its asset embedding +# at the sibling framework checkouts. +octos-app = { path = "../../../OctoSense-System-Apps/apps/appcard/app/app", default-features = false } # Tool arguments (`ask` takes `{"text": ...}`). serde_json = "1.0" diff --git a/apps/appcard/src/lib.rs b/apps/appcard/src/lib.rs index dd478e89..3f2c4711 100644 --- a/apps/appcard/src/lib.rs +++ b/apps/appcard/src/lib.rs @@ -19,12 +19,14 @@ //! kernel transport come up, exactly as in the APK) and draws the app's root //! body without its `Window{}`. The host's tile is the window. //! -//! The kernel is the app's own affair: `octos_app`'s `stdio_spawn` finds -//! `liboctos.so` in this APK's nativeLibraryDir (bundled with -//! `MAKEPAD_ANDROID_EXTRA_LIBS`, see docs/android-appcard-build.md), gives it -//! `HOME=/octos-home` and the memory budget, and runs `serve --stdio` -//! as a `kill_on_drop` child of the agent's runtime — so this module spawns -//! no kernel of its own (the Phase-A probe would have been a second child). +//! The kernel is the shell's, not the app's: `octosense_octos_core` (feature +//! `octos-core`, which `app-appcard` implies) finds `liboctos.so` in this +//! APK's nativeLibraryDir (bundled by `tools/android-kernel.py`, see +//! docs/android-appcard-build.md), gives it `HOME=/octos-home` and the +//! memory budget, and runs `serve --stdio` once per process when the first +//! consumer connects. The app's agent is one connection to it; this module +//! spawns no kernel of its own. When the AI providers change, the `llm` +//! service restarts the kernel and the agent's transport reconnects. //! //! What stays host-owned: the OS window and keyboard insets, and the //! notifications / share / WebView overlay of the standalone APK's Java @@ -75,8 +77,9 @@ impl AppModule for AppCardModule { root: root.clone(), executor: Box::new(AppCardExecutor { root }), // The host runs this before it drops the root: stop the agent — - // its tokio runtime and the kernel child (kill_on_drop) go with - // it — so nothing outlives the isolate. + // its tokio runtime and its kernel connection go with it (the + // kernel stops when its last consumer leaves) — so nothing + // outlives the isolate. shutdown: Box::new(move |_vm| { if let Some(mut inner) = shell.borrow_mut::() { inner.shutdown(); @@ -171,6 +174,7 @@ mod tests { storage: cx_storage_for_test(vm), viewport: makepad_app_module::Viewport { size: dvec2(400.0, 700.0) }, replies, + windows: Default::default(), }; let parts = APPCARD_MODULE.create(vm, open, handles); let errors = vm.take_errors(); diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index 3c596649..b7587232 100644 --- a/apps/reference/Cargo.toml +++ b/apps/reference/Cargo.toml @@ -6,5 +6,5 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d4502ef1e4d196d2829a07aa137740cb0581e9b4" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "6cf03859630761f5cb99ce7fcdfd8c30475d9ab8" } diff --git a/config/apps.json b/config/apps.json index 756c4644..c4d36887 100644 --- a/config/apps.json +++ b/config/apps.json @@ -58,14 +58,6 @@ "bin": "sheets", "policy": "focus" }, - { - "id": "photos", - "label": "Photos", - "source": "makepad", - "package": "makepad-photos", - "bin": "photos", - "policy": "focus" - }, { "id": "clock", "label": "Clock", @@ -90,14 +82,6 @@ "bin": "finance", "policy": "focus" }, - { - "id": "mail", - "label": "Mail", - "source": "makepad", - "package": "makepad-mail", - "bin": "mail", - "policy": "focus" - }, { "id": "notes", "label": "Notes", diff --git a/config/apps.makepad.json b/config/apps.makepad.json index 756c4644..c4d36887 100644 --- a/config/apps.makepad.json +++ b/config/apps.makepad.json @@ -58,14 +58,6 @@ "bin": "sheets", "policy": "focus" }, - { - "id": "photos", - "label": "Photos", - "source": "makepad", - "package": "makepad-photos", - "bin": "photos", - "policy": "focus" - }, { "id": "clock", "label": "Clock", @@ -90,14 +82,6 @@ "bin": "finance", "policy": "focus" }, - { - "id": "mail", - "label": "Mail", - "source": "makepad", - "package": "makepad-mail", - "bin": "mail", - "policy": "focus" - }, { "id": "notes", "label": "Notes", diff --git a/config/apps.overlay.json b/config/apps.overlay.json index 0291d183..148fa6bf 100644 --- a/config/apps.overlay.json +++ b/config/apps.overlay.json @@ -20,8 +20,11 @@ "bin": "route" } }, + "_drop": "mail and photos are OctoSense system apps (OctoSense-System-Apps, run by App Hub's Card runner) and take these ids; Makepad's example programs of the same name are not shipped.", "drop": [ - "scope" + "scope", + "mail", + "photos" ], "append": [ {"id": "image", "label": "Image Viewer", "source": "makepad", "package": "makepad-image", "bin": "image", "policy": "new"}, diff --git a/docs/add-all-makepad-apps.md b/docs/add-all-makepad-apps.md new file mode 100644 index 00000000..f38d736e --- /dev/null +++ b/docs/add-all-makepad-apps.md @@ -0,0 +1,191 @@ +# 把 Makepad 全家桶应用到 OctoSense + +本文说明如何把 Makepad 仓库里的全部应用(浏览器、文件、终端、天气、金融、邮件、笔记、日历、提醒、计算器、Fabric、Score、视频、导航、VJ、Fab、Director、Image、PDF、AI 等)一次性加进 OctoSense 桌面,让它们全部出现在 **⌘Space** 应用菜单里并可随时打开。 + +--- + +## 一、前提条件 + +1. **Makepad 同级框架仓库就位** + + ```sh + cd /path/to/OctoSense + python3 tools/setup-native.py --update + ``` + + 把 `../makepad`、`../octoscript`、`../octoscript-makepad` 检出到锁定版本。 + 目录里 `"source": "makepad"` 的行要靠 `cargo metadata --offline` 解析出 + 这个 checkout 才能生效。 + +2. **Cargo 缓存完整**(离线 metadata 能成功) + + 第一次构建宿主后,跑一次联网 `cargo metadata` 把缺失依赖补进缓存: + + ```sh + cargo metadata --format-version 1 --manifest-path Cargo.toml > /dev/null + ``` + + 之后 `cargo metadata --offline` 正常,目录解析出 Makepad 根。 + (缓存不全时所有 `source: makepad` 行会被静默跳过,日志只显示 + `no app 'X' in the registry`。) + +3. **编译宿主** + + ```sh + cargo build --release + ``` + +--- + +## 二、两个应用目录 + +| 文件 | 内容 | +|---|---| +| `config/apps.json`(默认) | Reference + Makepad 全家桶(20+ 个应用),桌面默认就读它 | +| `config/apps.makepad.json` | 与 `config/apps.json` 相同的副本,供 `--apps` 显式指定 | + +默认目录已经包含所有 Makepad 应用,你**不需要手写**,直接 `cargo run --release` 即可。 +也可以显式指定副本: + +```sh +cargo run --release -- --apps config/apps.makepad.json +``` + +想换成自己的目录,建 `~/.octosense/apps.json` 个人目录覆盖默认目录,重启即可。 + +> 注意:Makepad 自带的 Photos 和 Mail 已从目录中移除,由 OctoSense 的系统应用(News、Photos、Maps、Camera、Mail、AI providers,经 App Hub 打包进 Shell)取代,它们不在开发者程序目录里。 + +--- + +## 三、目录里 Makepad 全家桶的完整条目 + +`config/apps.makepad.json` 的 `source: makepad` 行(每行 3 要素: +`package` = Makepad 的 Cargo 包名,`bin` = 可执行文件名,`policy` = 多实例策略): + +| id | label | package | bin | policy | +|---|---|---|---|---| +| `browser` | Browser | makepad-browser | browser | focus | +| `files` | Files | makepad-files | files | focus (args: `--demo`) | +| `terminal` | Terminal | makepad-terminal | terminal | new | +| `mixer` | Mixer | makepad-mixer | makepad-mixer | focus | +| `task` | Task Manager | makepad-task | task | focus | +| `sheets` | Sheets | makepad-sheets | sheets | focus | +| `clock` | Clock | makepad-clock | clock | focus | +| **`weather`** | **Weather** | **makepad-weather** | **weather** | **focus** | +| `finance` | Finance | makepad-finance | finance | focus | +| `notes` | Notes | makepad-notes | notes | focus | +| `calendar` | Calendar | makepad-calendar | calendar | focus | +| `reminders` | Reminders | makepad-reminders | reminders | focus | +| `calculator` | Calculator | makepad-calculator | calculator | focus | +| `fabric` | Fabric | makepad-fabric | makepad-fabric | focus | +| `score` | Score | makepad-app-score | makepad-app-score | focus | +| `video` | Video Player | makepad-video | video | new | +| `route` | Route | makepad-app-route | route | focus | +| `vj` | VJ | makepad-vj | makepad-vj | focus | +| `fab` | Fab | makepad-fab | makepad-fab | focus | +| `studio` | Director | makepad-director | director | focus | +| `image` | Image Viewer | makepad-image | image | new | +| `pdf` | PDF Viewer | makepad-pdf | pdf | new | +| `aichat` | AI | makepad-aichat | aichat | new | + +`policy: focus` = 已有实例就聚焦,`policy: new` = 每次开新实例。 + +--- + +## 四、启动并打开全家桶 + +```sh +# 1. 启动桌面,加载 Makepad 全家桶目录 +cargo run --release -- --apps config/apps.makepad.json +``` + +启动后用任一方式打开应用: + +**脚本化**(每个应用独立一行,适合自动化): + +```sh +# 在启动参数里同时开多个(可叠加) +cargo run --release -- \ + --apps config/apps.makepad.json \ + --test-action launch-weather \ + --test-action launch-browser + +# 或者在桌面已运行时,另开一个 shell 发 test-action +# (test-action 只在启动时执行一次,所以每次要新起进程) +``` + +**手动**(在桌面里操作,最常用): + +- **⌘Space** → 应用菜单,搜索并点击任意应用名 +- 或 **⌘1…0** 切工作区,**⌘W** 关 tile,**⌘F** 当前 tile 全屏 + +每个应用首次打开会现场 `cargo run` 编译(进度显示在 tile 里, +首次冷构建约 90 秒);之后复用 Cargo 缓存,秒开。 + +--- + +## 五、验证 + +启动后在桌面主日志(重定向 stdout 可得)里找: + +``` +wm: desktop style octosense applied to N clients and 0 modules +# N = 已打开的应用 tile 数 + +# 对每个打开的应用: +wm: --test-action launch weather +wm: launched weather as client 1 +wm: weather client 1 first frame in NNN ms (cold) ← 出画面 + +# 打开多个应用: +wm: launched browser as client 2 +wm: launched terminal as client 3 +... +``` + +`first frame in NNN ms (cold)` 表示该应用的首帧已渲染,tile 里能看到画面。 +每个应用自己的日志在 `~/.octosense/wm/logs/.log`。 + +--- + +## 六、常见问题 + +**打开应用时报 `no app 'X' in the registry`** + +Cargo 离线缓存不全。联网跑一次: + +```sh +cargo metadata --format-version 1 --manifest-path Cargo.toml > /dev/null +``` + +再重启 OctoSense。 + +**应用 tile 里一直显示 "building…" 不出画面** + +首次冷构建,进度正常。等 90 秒左右出首帧。之后秒开。 + +**应用打不开(构建失败)** + +看 `~/.octosense/wm/logs/.log` 里的编译错误。 +Makepad 全家桶应用都依赖同一个 Makepad checkout,版本不匹配会失败。 + +**某个应用不想要,隐藏它的启动器图标** + +在 `~/.octosense/wm/launcher.hides` 里加一行应用 id,例如: + +```json +["image", "pdf", "aichat"] +``` + +Image/PDF/AI 这三个 helper 应用默认出现在启动器里(除非被隐藏)。 + +--- + +## 相关 + +- 单个应用目录格式:README「Add an app」章节 +- 目录解析原理与离线 metadata 踩坑:[open-apps.md](open-apps.md) 第四、五章 +- Makepad 全家桶目录是生成物:由上游 `scripts/upstream.py catalog`(读 + Makepad checkout 的依赖图生成)产出,本地在 `config/apps.overlay.json` + 叠加适配。检查漂移或重新生成: + `python3 scripts/upstream.py catalog`(加 `--apply` 写回)。 diff --git a/docs/android-appcard-build.md b/docs/android-appcard-build.md index 3c0cc602..42824f91 100644 --- a/docs/android-appcard-build.md +++ b/docs/android-appcard-build.md @@ -1,8 +1,19 @@ # Android build with AppCard's framework, buildtool and bundled kernel -OctoSense on the phone now runs the Octoscript-AppCard module (`apps/appcard`) -on the makepad fork's AppCard framework line and needs three things the stock -`cargo makepad` build does not give it: +> The octos kernel is a shell service now (`octosense-octos-core`, feature +> `octos-core`, always on for Android), not AppCard's: **every** APK must +> bundle `liboctos.so`. `tools/android-kernel.py` does it (it cross-builds +> the kernel at the revision `Cargo.lock` pins, `--no-default-features +> --features api,git,ast`, and runs the packager with +> `MAKEPAD_ANDROID_EXTRA_LIBS`). AppCard itself is not shipped for now and +> links only with `--features app-appcard`. The steps below are the manual +> equivalent; their pins are older than the current ones. + +OctoSense on the phone now runs the AppCard assistant module (`apps/appcard`, +which mounts `octos-app` from the pinned OctoSense-System-Apps checkout, +`../OctoSense-System-Apps/apps/appcard/app/app`) on the makepad fork's AppCard +framework line and needs three things the stock `cargo makepad` build does not +give it: 1. **The framework pin.** `Cargo.toml`, `apps/reference/Cargo.toml` and `apps/appcard/Cargo.toml` pin every makepad crate at the fork's @@ -21,18 +32,20 @@ on the makepad fork's AppCard framework line and needs three things the stock `dev.makepad.octosense`, not persistent). 3. **The octos kernel**, cross-built for `aarch64-linux-android` and bundled into the APK as `liboctos.so`. Android lets an app exec only from its - nativeLibraryDir, so the kernel must ship as a "library"; the hosted app - (`octos-app`'s `stdio_spawn`) finds it there and runs `octos serve --stdio` - with `HOME=/octos-home`. + nativeLibraryDir, so the kernel must ship as a "library"; the shell's + kernel service (`octosense-octos-core`) finds it there and runs `octos + serve --stdio` with `HOME=/octos-home` when the first consumer + (AppCard, Rinx) connects. ## Step by step ```sh -# 0. Paths (adjust): a checkout of the fork and of Octoscript-AppCard (with its -# `octos` submodule initialised), and the Android toolchain dir that -# `cargo makepad android install-toolchain` produced earlier. +# 0. Paths (adjust): a checkout of the fork, a checkout of octos-org/octos at +# the rev Cargo.lock pins for octos-cli (6ad76e5c today; the same rev +# OctoSense-System-Apps pins), and the Android +# toolchain dir that `cargo makepad android install-toolchain` produced. FORK=/path/to/makepad-fork # https://github.com/OctoSense-org/makepad.git -APPCARD=/path/to/Octoscript-AppCard # octos submodule at deb433e9 or later +OCTOS=/path/to/octos # https://github.com/octos-org/octos.git at 6ad76e5c1e659bdf10ec05ae869428b48edccf7f TOOLCHAIN=/path/to/android_33_macos_aarch64 # ndk/, platforms/, build-tools/, platform-tools/, openjdk/ # 1. The buildtool cargo-makepad. It looks for the toolchain relative to its @@ -54,9 +67,9 @@ export CC_aarch64_linux_android="$LLVM/aarch64-linux-android33-clang" export CXX_aarch64_linux_android="$LLVM/aarch64-linux-android33-clang++" export AR_aarch64_linux_android="$LLVM/llvm-ar" export RANLIB_aarch64_linux_android="$LLVM/llvm-ranlib" -( cd "$APPCARD/octos" && cargo build --release --target aarch64-linux-android \ +( cd "$OCTOS" && cargo build --release --target aarch64-linux-android \ -p octos-cli --bin octos --features api,git,ast ) -KERNEL="$APPCARD/octos/target/aarch64-linux-android/release/octos" # ~130 MB +KERNEL="$OCTOS/target/aarch64-linux-android/release/octos" # ~130 MB # 3. Build, install and launch OctoSense with the kernel bundled. From this # repository's root, with the phone authorised over adb: @@ -68,8 +81,8 @@ The exact invocation used for the foundation PR, with the paths of that session, for the record: ```sh -MAKEPAD_ANDROID_EXTRA_LIBS="liboctos.so=/private/tmp/claude-505/-Users-user-home-splash/0bdb704b-c531-48c0-b6a5-009881090313/scratchpad/appcard-apk/octos/target/aarch64-linux-android/release/octos" \ - /private/tmp/claude-505/-Users-user-home-splash/0bdb704b-c531-48c0-b6a5-009881090313/scratchpad/bt-1b11c4a/target/debug/cargo-makepad \ +MAKEPAD_ANDROID_EXTRA_LIBS="liboctos.so=/abs/path/to/octos/target/aarch64-linux-android/release/octos" \ + /abs/path/to/makepad-buildtool/target/debug/cargo-makepad \ makepad android run -p octosense --release ``` @@ -83,25 +96,25 @@ unzip -l target/makepad-android-apk/octosense/apk/octosense.apk | grep liboctos "$ADB" logcat -d -s Makepad | grep -E 'stdio:|kernel' # the kernel's start ``` -When the AppCard tile starts (the module delivers `Event::Startup` to the -hosted app on first contact), the app's agent spawns the kernel over stdio -and logs one of - -- `stdio: octos=…/lib/arm64/liboctos.so HOME=…/files/octos-home` — the bundled - kernel was found and started; the agent's `client_hello` follows; -- `stdio: bundled octos not found under …; using WebSocket transport` — the - APK was built without `MAKEPAD_ANDROID_EXTRA_LIBS`; the app shows its - login screen. - -The kernel is a `kill_on_drop` child of the agent's runtime: the module's -`shutdown` stops the agent when the tile's instance is torn down, and the -child goes with it. The module spawns no kernel of its own. - -On a desktop the app spawns a local kernel only when BOTH `OCTOS_APP_CORE_BIN` -(the `octos` binary) and `OCTOS_APP_CORE_DIR` (its data dir; the app runs -`serve --stdio --data-dir --config /config.json` with -`OCTOS_HOME=`) are set; otherwise it uses the WebSocket transport / login -screen, so a developer's own `octos serve` is never touched. +At startup the shell logs `octos: kernel service ready (starts on first use), +core dir …` (or `octos: no octos kernel: …/liboctos.so is not bundled`). When +the AppCard tile starts (the module delivers `Event::Startup` to the hosted +app on first contact), the app's agent connects to the kernel service, which +starts the kernel and logs `octos-core: starting kernel 1: …/liboctos.so +serve --stdio`; without a kernel the agent logs `kernel: no octos kernel: …; +using the WebSocket transport` and the app shows its login screen. + +The kernel is the shell's, one per process: the module's `shutdown` drops the +agent's connection when the tile's instance is torn down, and the kernel stops +when its last consumer leaves. When the AI providers change, the `llm` service +restarts it and the agent reconnects. + +On a desktop the kernel service runs a local kernel only when +`OCTOS_APP_CORE_BIN` names the `octos` binary (data dir `OCTOS_APP_CORE_DIR`, +else `~/octos-home/.octos`; it runs `serve --stdio --data-dir `, plus +`--config /config.json` when that file exists, with `OCTOS_HOME=`); +otherwise AppCard uses the WebSocket transport / login screen, so a +developer's own `octos serve` is never touched. ## Provisioning the LLM key diff --git a/docs/local-ai.md b/docs/local-ai.md index c1eb6d33..0abb4ce7 100644 --- a/docs/local-ai.md +++ b/docs/local-ai.md @@ -20,7 +20,7 @@ makepad_rev=$(python3 -c 'import json; print(json.load(open("upstream/makepad.js git -C ../makepad checkout --detach "$makepad_rev" ``` -For an existing checkout, keep its revision aligned through the [upstream workflow](upstream.md). A personal app catalog must also contain an `aichat` entry; see [app registration](../README.md#add-an-app). Opening the assistant builds it through Cargo on first launch. It does not require enabling the host's optional `app-aichat` feature. +For an existing checkout, keep its revision aligned through the [upstream workflow](upstream.md). A personal app catalog must also contain an `aichat` entry; see [developer programs and the catalog](../README.md#developer-programs-and-the-catalog). Opening the assistant builds it through Cargo on first launch. It does not require enabling the host's optional `app-aichat` feature. ## Install the tested model diff --git a/docs/open-apps.md b/docs/open-apps.md new file mode 100644 index 00000000..dd891f92 --- /dev/null +++ b/docs/open-apps.md @@ -0,0 +1,147 @@ +# 在 OctoSense 中打开托管应用(操作记录 + 参考) + +这份文档整理了在 OctoSense 桌面中打开托管应用的步骤(2026-09-26 实测)。 + +OctoSense 桌面能把 Makepad 应用作为独立子进程托管在自己的窗口(tile)里。 +下面先按时间顺序记录当时怎么做的,再给可照抄的命令。 + +## 一、当时是怎么操作的(按时间) + +1. **准备同级框架仓库**:`python3 tools/setup-native.py --update` + —— 把 `../makepad`、`../octoscript`、`../octoscript-makepad` 检出到锁定版本。 +2. **编译宿主**:`cargo build`(debug 或 release),产物在 `CARGO_TARGET_DIR` + 指定的 target 目录下(默认 `target/`)。 +3. **启动桌面**:跑编译出的 `octosense` 可执行文件。 + 第一次用 `--test-action launch-weather` 打 weather,日志报 + `no app 'weather' in the registry` —— 应用目录里查不到 weather。 +4. **查原因**:目录的 `"source": "makepad"` 行要靠 + `cargo metadata --offline` 解析出 Makepad checkout; + 而离线 metadata 因为缓存缺依赖(`agent-client-protocol` 1.3.0)而失败, + 导致所有 makepad 应用行被静默跳过。 +5. **修复**:联网跑一次 `cargo metadata` 把缺的依赖补进缓存; + 之后离线 metadata 正常,目录解析出同级的 `../makepad` checkout。 +6. **再开 weather**:桌面把 weather 作为 **client 1** 托管,现场 + `cargo run` 编译;首次冷构建 91 秒出首帧(`first frame in 91473 ms (cold)`), + 天气应用成功渲染进桌面 tile。 + +## 二、可照抄的命令 + +### 启动并打开天气 + +```sh +# 启动桌面(用 Makepad 全家桶目录) +cargo run --release -- --apps config/apps.makepad.json + +# 在另一个 shell 里,或直接加在启动参数上: +cargo run --release -- --test-action launch-weather +``` + +`--test-action launch-` 是脚本化打开应用的方式, +app-id 就是目录里的 `id`(weather、browser、files、terminal、aichat……)。 +首次启动会现场 `cargo run` 编译应用,进度显示在 tile 里; +之后复用 Cargo 缓存,秒开。 + +### 手动操作(不用 test-action) + +- **⌘Space** 打开应用菜单/搜索,直接搜 "Weather" +- **⌘W** 关 tile,**⌘F** 当前 tile 全屏,**⌘1…0** 切工作区 +- 菜单里 Learn → Keybindings 列全部快捷键 + +### 验证日志 + +启动后在桌面主日志(重定向 stdout 可得)里找这几行: + +``` +wm: --test-action launch weather +wm: launched weather as client 1 +wm: weather client 1 first frame in NNN ms (cold) ← 应用已出画面 +``` + +## 三、两个应用目录 + +| 文件 | 内容 | 何时用 | +|---|---|---| +| `config/apps.json`(默认) | Reference + Makepad 全家桶(Browser、Files、Terminal、Weather、Finance、Notes、Calendar、Reminders、Calculator、Fabric、Score、Video、Route、VJ、Fab、Director、Image、PDF、AI 等) | 默认桌面 | +| `config/apps.makepad.json` | 与默认目录相同的副本 | 用 `--apps` 显式指定时 | + +用 `--apps` 选目录: + +```sh +cargo run --release -- --apps config/apps.makepad.json +``` + +个人目录 `~/.octosense/apps.json` 优先级高于项目默认;`--apps` 永远指向你点名的文件。 + +## 四、目录里为什么应用会"缺席" + +`"source": "makepad"` 的行不是按路径找,而是让 Cargo 报告它用哪个 Makepad checkout +来编译本宿主,然后去那个 checkout 里找应用。 + +1. 宿主启动时跑一次 `cargo metadata --offline --locked`, + 在依赖图里找带 `apps/wm/Cargo.toml` 标记的 Makepad 仓库根。 + (见 `src/octosense/makepad_source.rs`、`src/octosense/catalog.rs`。) +2. 找不到 checkout(比如离线依赖不全),这些行会被**静默跳过**, + 不会报错 —— 日志里只会看到 `no app 'X' in the registry`。 + +症状:启动后用 `--test-action launch-weather` 打开应用, +日志打 `octosense: no app 'weather' in the registry`,目录里却没有 weather。 + +原因:Cargo 缓存里缺少 lock 文件锁定的依赖版本(本次是 +`agent-client-protocol` 要 1.3.0,缓存只有 1.2.0), +`cargo metadata --offline` 直接失败 → 解析不出 Makepad 根 → +所有 `source: makepad` 行被丢弃。 + +修复(联网跑一次 metadata 把依赖补进缓存): + +```sh +cd OctoSense +cargo metadata --format-version 1 --manifest-path Cargo.toml > /dev/null +``` + +之后离线 metadata 正常,目录解析出同级的 `../makepad` checkout。 +(前提:同级框架仓库已经用 `python3 tools/setup-native.py` 准备好。) + +## 五、往目录里加一个新应用 + +目录是一组 JSON 数组,每行三选一: + +- `"manifest"` + `"package"` + `"bin"` —— 指到某个 Cargo 工程(相对目录所在目录) +- `"executable"` —— 指到已安装的可执行文件 +- `"source": "makepad"` + `"package"` + `"bin"` —— 让宿主按本构建的 Makepad checkout 解析 + +```json +{ + "id": "notes", + "label": "Notes", + "manifest": "../notes/Cargo.toml", + "package": "my-notes", + "bin": "notes", + "policy": "new", + "args": [] +} +``` + +规则(与 `src/octosense/catalog.rs` 校验一致): + +- `id` 只允许小写字母、数字、`-`、`_`;目录内不允许重复 +- `label` 必填;`policy` 缺省是 `focus`(已有实例就聚焦),`new` 强制开新实例 +- `args` 是字符串数组,原样传给子进程(不经 shell) +- 应用必须支持 `--stdin-loop` 托管协议,且与本宿主同一 Makepad 版本 +- 可执行文件托管的条目不要写 `package` / `bin` + +放哪: + +```sh +# 项目默认目录:改 config/apps.json,重启 OctoSense +# 个人目录(优先于项目默认):~/.octosense/apps.json +# 临时选目录:--apps /path/to/your.json +``` + +写完之后,应用就会出现在 **⌘Space** 应用菜单里;也可以 `--test-action launch-` 直接打开。 +不可用(找不到 manifest / 构建失败)的应用会在桌面日志里报原因,不会崩溃。 + +## 相关 + +- 应用目录格式:README「Add an app」章节 +- 每个应用托管时自己的日志:`~/.octosense/wm/logs/.log` +- 目录里不存在的 app 快捷键会提示「unavailable」,不会崩溃 diff --git a/docs/plans/2026-09-04-makeos-extraction.md b/docs/plans/2026-09-04-makeos-extraction.md index 4f8286ce..cd57dc6b 100644 --- a/docs/plans/2026-09-04-makeos-extraction.md +++ b/docs/plans/2026-09-04-makeos-extraction.md @@ -2,7 +2,6 @@ > Historical record from before the OctoSense rename. Original names, commands and artifact paths are retained for traceability. -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task after the user agrees to the scope. **Goal:** Create a minimal MakeOS project in this directory that opens with `cargo run`, hosts compatible Makepad applications, and records every copied upstream file for deliberate updates. diff --git a/docs/validation.md b/docs/validation.md index bcd79961..5afbed59 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -431,3 +431,28 @@ The diagnostic call stack is in `target/upstream-20260911/trace-tap/host.log`. The starting dirty working tree is preserved in `target/upstream-20260911/before/`; the integration diff and exact file list are saved beside it. Source checkouts were read only. + +## App Hub, system apps and the AppCard repin, 2026-09-26 + +Branch `apps/app-hub-system-apps`, on macOS 26 (Apple silicon), with the +siblings prepared by `tools/setup-native.py` (makepad at the patched tree +`3889a349`, OctoSense-System-Apps `4d99cb58`). + +- `cargo check --workspace`, and with `--features mobile-apps`: clean. +- `cargo test --workspace`: 228 passed; with `--features mobile-apps`: 231 + passed (the bundled-catalog test lists the five system apps as Card apps and + opens every native module). `cargo test -p octosense-mail-service`: 7 passed. +- `python3 -m unittest discover -s scripts -p 'test_*.py'`: 57 passed. +- `python3 scripts/upstream.py catalog`: the catalog matches the pinned + revision under the overlay (which now drops `mail` and `photos`). +- `setup-native.py --check --cargo-manifest Cargo.toml` on a fresh sibling + workspace: one Makepad, one octos (`octos-core` from octos-org/octos + `18fcd3f1` only, also with `--features app-rinx`: Rinx `5ad2c173` pins no + octos), one `octosense-appstore`. +- Release run with `MAKEPAD_REMOTE` and `MAKEPAD_APP_CONFIG='{"mail_demo":true}'` + (isolated `OCTOSENSE_HOME` and `OCTOSENSE_APP_DATA`): the dock and launcher + list App Hub, News, Photos, Maps, Camera and Mail; Mail opened in the Card + runner (`card: os.mail running under 2 capability(ies)`), its sign-in sheet + drew over the app, the demo account (password `demo`) signed in and the + inbox listed the demo messages; Photos opened on its Collections page with + the bundle's thumbnails. No keychain prompt: the demo uses the file vault. diff --git a/native-apps.lock.json b/native-apps.lock.json new file mode 100644 index 00000000..bbc4d287 --- /dev/null +++ b/native-apps.lock.json @@ -0,0 +1,9 @@ +{ + "schema_version": 1, + "repositories": { + "system-apps": { + "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", + "revision": "033503482b1beb6e6f1d79c5fddaa3254b7dfc85" + } + } +} diff --git a/native-runtime.lock.json b/native-runtime.lock.json index 095afdca..105eb060 100644 --- a/native-runtime.lock.json +++ b/native-runtime.lock.json @@ -1,5 +1,5 @@ { "schema_version": 1, "url": "https://github.com/OctoSense-org/Octoscript-Makepad.git", - "revision": "b1596d9c2cee6e80cca9d27b1b0c2d57b9d28dd8" + "revision": "6881fb6c3c3220e407633b0ba5211c3d42c7e625" } diff --git a/runtime-patches.lock.json b/runtime-patches.lock.json new file mode 100644 index 00000000..bf00d958 --- /dev/null +++ b/runtime-patches.lock.json @@ -0,0 +1,3 @@ +{ + "schema_version": 1 +} diff --git a/scripts/ai_providers_remote.sh b/scripts/ai_providers_remote.sh new file mode 100755 index 00000000..778e51e2 --- /dev/null +++ b/scripts/ai_providers_remote.sh @@ -0,0 +1,667 @@ +#!/usr/bin/env bash +# AI providers' QR import from an image, and the phone QR it exports, on the +# desktop, driven through Makepad's remote (instrument) mode: no OS +# screenshots, only the app's own frames. +# +# cargo build --release [--config …] --features mobile-apps +# cargo build --release [--config …] -p octosense-llm-service --example read_qr_image +# scripts/ai_providers_remote.sh [artifacts-dir] +# +# Runs the release binary hidden with its own OCTOSENSE_HOME, octos core dir +# and file vaults under a temp directory (neither ~/.octosense nor the login +# keychain is touched). Every key in it is fake, and the binary runs under +# sandbox-exec with outbound HTTPS denied: no fake key reaches a real +# provider, and a provider test fails as a network failure. A local fake +# endpoint (fake.py, 127.0.0.1) stands in for a provider that answers. The +# flow: +# 1. AI providers opens; the host reports an image picker ("Import code +# from image"). +# W1. Add DeepSeek with the add wizard, step by step: 1 search the families +# and choose DeepSeek, 2 the model pull-down (DeepSeek V4 Flash, +# Recommended), 3 the route pull-down (Official API), 4 a fake key, 5 +# Test connection: the provider cannot be reached, Save stays disabled +# and "Save without testing" saves it as the primary. +# W2. Change its model with the card's model pull-down (DeepSeek V4 Pro). +# W3. Add Z.ai as a fallback on another model (GLM-4.7) and route (a +# custom OpenAI-compatible endpoint: the fake endpoint); Test connection +# passes ("Connected"), and "Add as fallback" saves it. +# W4. The saved-models list and _main.json say so; Test connection on a +# row; then both are removed with the cards' Remove (tap twice). +# 2. Import into the empty list by PASTE: an old single-provider code +# (DeepSeek deepseek-v4-flash, fake key ending fcb0) typed into the +# import sheet's code field is saved as the primary. +# 3. Add OpenAI with the wizard (its default gpt-4o on the official route) +# with the fake key sk-test-0000000000001234, saved without testing. +# 4. Import by DROP: the fixture QR-A (OctoSense-System-Apps +# config/tests/fixtures/qr-a.png, PIN 7K3M-9QX2: DeepSeek deepseek-chat +# and Z.ai glm-4.6) dropped on the import sheet through the native drop +# path (/drop), PIN typed. An import only adds, so there is no confirm +# step: the primary and OpenAI stay first, QR-A's providers are appended +# as fallbacks, and its DeepSeek key goes to a slot of its own +# (DEEPSEEK_2_API_KEY) so the primary's key is not overwritten. +# 5. Import QR-A again by PICKER: "Choose image" (OCTOSENSE_LLM_TEST_IMAGE +# answers with the fixture instead of the open panel, which a hidden run +# cannot click through); a wrong PIN is refused and changes nothing, the +# right one changes nothing either ("Already saved"). +# 6. Show QR for phone, grab the sheet, read the QR out of the grab (the +# service's own image search, rqrr) and open it with the PIN on the +# sheet: it equals the saved set and keys, both DeepSeek slots included. +# The countdown +# ticks, and at 0 the sheet closes by itself (OCTOSENSE_LLM_QR_SECONDS +# shortens its five minutes to $QR_SECONDS s), with no Splash error in +# the log. +# 7. No key text in /snap, /d, /log or the host log; no panic, no Splash +# error; /gq exits. +# Artifacts: grabs/, export.png (the export sheet at full scale) and +# export-pin.txt, for scanning on a phone. The script-drawn controls (the +# wizard's fields, rows and pull-downs, the app's cards) are not in /snap: +# they are found in grabs by their colours (blocks.py). +set -euo pipefail + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +BIN=${OCTOSENSE_BIN:-$ROOT/target/release/octosense} +READ_QR=${READ_QR_IMAGE:-$ROOT/target/release/examples/read_qr_image} +SRC=$(cd "$ROOT" && python3 -c 'import json; print(json.load(open("system-apps.json"))["source"])') +FIXTURE=$(cd "$ROOT/$SRC/ai-providers/config/tests/fixtures" && pwd)/qr-a.png +FIXTURE_PIN=7K3M-9QX2 +FAKE_KEY=sk-test-0000000000001234 +# The old single-provider code the empty list is filled from (its key is fake). +LEGACY_CODE='{"llm_family":"deepseek","llm_model":"deepseek-v4-flash","llm_key":"sk-test-legacy-00000000fcb0"}' +# The phone QR's lifetime in this run: long enough to grab and read the code. +QR_SECONDS=${QR_SECONDS:-20} +# What a Splash script error logs (the export sheet's countdown used to log +# these every second). +SPLASH_ERRORS='pop_stack_resolved|mes empty|splash host callback error|\[E\] splash' +# Every key the run handles: none may show up in the UI tree or the logs. +SECRETS='sk-test-|zai-test-|0000000000001234|0000000000005678' +ZAI_KEY=sk-test-0000000000005678 +WORK=${1:-$(mktemp -d -t octosense-ai-providers)} +mkdir -p "$WORK/home" "$WORK/octos" "$WORK/grabs" +LOG=$WORK/host.log +PROFILE=$WORK/octos/profiles/_main.json +[ -x "$BIN" ] || { echo "no $BIN: build it first"; exit 2; } +[ -x "$READ_QR" ] || { echo "no $READ_QR: cargo build --release -p octosense-llm-service --example read_qr_image"; exit 2; } +echo "artifacts: $WORK" + +cd "$WORK" +: >"$LOG" +env -u MAKEPAD_HOME -u MAKEPAD_WM_ROOT -u MAKEPAD_WM_THEME \ + OCTOSENSE_HOME="$WORK/home" OCTOS_APP_CORE_DIR="$WORK/octos" \ + OCTOSENSE_MAIL_VAULT=file OCTOSENSE_LLM_VAULT=file \ + OCTOSENSE_LLM_TEST_IMAGE="$FIXTURE" OCTOSENSE_LLM_QR_SECONDS="$QR_SECONDS" \ + MAKEPAD_HIDE_WINDOWS=1 sandbox-exec -p '(version 1)(allow default)(deny network-outbound (remote tcp "*:443"))' \ + "$BIN" --remote >"$LOG" 2>&1 & +PID=$! +PORT= +# A provider that answers: OpenAI-compatible chat completions for the Z.ai +# fake key, on 127.0.0.1 (the sandbox lets local traffic through). +cat >"$WORK/fake.py" <<'EOF' +import http.server, json, sys +KEY = sys.argv[1] +class H(http.server.BaseHTTPRequestHandler): + def answer(self, ok, body): + data = json.dumps(body if ok else {"error": {"message": "Incorrect API key"}}).encode() + self.send_response(200 if ok else 401) + self.send_header("content-type", "application/json"); self.send_header("content-length", str(len(data))) + self.end_headers(); self.wfile.write(data) + def do_POST(self): + self.rfile.read(int(self.headers.get("content-length", 0))) + self.answer(self.headers.get("authorization") == "Bearer " + KEY, {"choices": []}) + def do_GET(self): + self.answer(self.headers.get("authorization") == "Bearer " + KEY, {"data": [{"id": "glm-4.7"}]}) + def log_message(self, *args): pass +server = http.server.HTTPServer(("127.0.0.1", 0), H) +print(server.server_address[1], flush=True) +server.serve_forever() +EOF +python3 "$WORK/fake.py" "$ZAI_KEY" >"$WORK/fake.port" 2>/dev/null & +FAKE=$! +disown "$FAKE" +cleanup() { + kill "$FAKE" 2>/dev/null || true + if kill -0 "$PID" 2>/dev/null; then + [ -n "$PORT" ] && curl -s "127.0.0.1:$PORT/quit" >/dev/null || true + sleep 2 + kill "$PID" 2>/dev/null || true + fi +} +trap cleanup EXIT + +fail() { echo "FAIL: $*"; [ -z "${HOLD_ON_FAIL:-}" ] || sleep "$HOLD_ON_FAIL"; exit 1; } +pass() { echo "PASS: $*"; } +for _ in $(seq 1 120); do + PORT=$(sed -n 's/.*\[makepad-remote\] listening on 127\.0\.0\.1:\([0-9]*\) pid=.*/\1/p' "$LOG" | head -1) + [ -n "$PORT" ] && break + sleep 0.5 +done +[ -n "$PORT" ] || fail "no remote port in $LOG" +get() { curl -fsS "127.0.0.1:$PORT/$1"; } +q() { python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$1"; } +key() { + local out + out=$(curl -s "127.0.0.1:$PORT/k?k=press&c=$1&wait=1${2:-}") + case $out in *'"err"'*) echo "note: key $1: $out" ;; esac +} +type_keys() { + local text=$1 i c + for ((i = 0; i < ${#text}; i++)); do + c=${text:i:1} + case $c in + " ") key Space ;; + *) key "Key$(printf %s "$c" | tr a-z A-Z)" ;; + esac + done +} +# Input waits for the next frame; a hidden window sometimes misses it and +# the remote answers "retry" although the input was delivered. What input +# did is checked through the profile, the log and snapshots, so a missed +# frame is only noted (retrying could click twice). +input() { + local out + out=$(curl -s "127.0.0.1:$PORT/$1&wait=1") + case $out in *'"err"'*) echo "note: ${1%%\?*}: $out" ;; esac +} +text() { input "t?t=$(q "$1")"; } +click() { input "click?x=$1&y=$2"; } +scroll() { input "m?k=scroll&x=$1&y=$2&dy=$3"; } +# A grab at layout scale (one pixel a point); prints its path. +grab() { + local png + for _ in 1 2 3 4 5; do + png=$(curl -s "127.0.0.1:$PORT/g?scale=${2:-0.5}" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("png",""))' || true) + [ -n "$png" ] && { cp "$png" "$WORK/grabs/$1.png"; echo "$WORK/grabs/$1.png"; return; } + sleep 1 + done + fail "no frame for $1" +} +# The centre of the card widget whose text is exactly $1: "x y". +widget() { + get "snap?q=$(q "$1")" | python3 -c ' +import json,sys +hits=[e for e in json.load(sys.stdin)["s"] if e.get("t")==sys.argv[1] and e["r"][2]>0] +if hits: x,y,w,h=hits[-1]["r"]; print(x+w/2, y+h/2)' "$1" +} +wait_widget() { + for _ in $(seq 1 ${2:-40}); do [ -n "$(widget "$1")" ] && return 0; sleep 0.5; done + fail "never showed: $1" +} +# The host's sheet (its rect and its program) once it runs `$1`. +sheet_rect() { + get "snap?q=$(q "$1")" | python3 -c ' +import json,sys +hits=[e for e in json.load(sys.stdin)["s"] if e["i"]=="sheet" and sys.argv[1] in (e.get("t") or "")] +if hits: print(*hits[-1]["r"])' "$1" +} +wait_sheet() { + local r + for _ in $(seq 1 40); do r=$(sheet_rect "$1"); [ -n "$r" ] && { echo "$r"; return 0; }; sleep 0.5; done + fail "no sheet running $1" +} +wait_log() { + for _ in $(seq 1 ${2:-40}); do grep -q "$1" "$LOG" && return 0; sleep 0.5; done + fail "log never said: $1" +} +# Sheets are Splash isolates whose widgets /snap does not list: find their +# controls in a grab instead. Prints "pill X Y" (the blue action at the top +# right: Import, Save, Close) and "full Y" / "half X Y" for each grey field +# or button, top to bottom. +cat >"$WORK/sheet.py" <<'EOF' +import sys, zlib, struct +def read_png(path): + data = open(path, "rb").read() + pos, chunks = 8, {} + idat = b"" + while pos < len(data): + n, kind = struct.unpack(">I4s", data[pos:pos + 8]) + body = data[pos + 8:pos + 8 + n] + if kind == b"IHDR": w, h, depth, color = struct.unpack(">IIBB", body[:10]) + if kind == b"IDAT": idat += body + pos += 12 + n + assert depth == 8 and color in (2, 6), (depth, color) + bpp = 4 if color == 6 else 3 + raw, stride, rows, prev = zlib.decompress(idat), w * bpp, [], bytearray(w * bpp) + i = 0 + for _ in range(h): + f, line = raw[i], bytearray(raw[i + 1:i + 1 + stride]); i += 1 + stride + for x in range(stride): + a = line[x - bpp] if x >= bpp else 0 + b = prev[x]; c = prev[x - bpp] if x >= bpp else 0 + if f == 1: line[x] = (line[x] + a) & 255 + elif f == 2: line[x] = (line[x] + b) & 255 + elif f == 3: line[x] = (line[x] + (a + b) // 2) & 255 + elif f == 4: + p = a + b - c; pa, pb, pc = abs(p - a), abs(p - b), abs(p - c) + line[x] = (line[x] + (a if pa <= pb and pa <= pc else b if pb <= pc else c)) & 255 + rows.append(bytes(line)); prev = line + return w, h, bpp, rows +path, sx, sy, sw, sh = sys.argv[1], *map(float, sys.argv[2:6]) +w, h, bpp, rows = read_png(path) +px = lambda x, y: rows[y][x * bpp:x * bpp + 3] +x0, x1 = int(sx + 20), int(sx + sw - 20) +inner = x1 - x0 +grey = lambda p: 200 <= p[0] <= 248 and abs(p[0] - p[1]) <= 2 and 3 <= p[2] - p[0] <= 7 +blue = lambda p: p[0] < 60 and 100 <= p[1] <= 160 and p[2] > 200 +# The pill: the blue columns tall enough to be a filled button. +top = [y for y in range(int(sy), int(sy + 110))] +cols = [x for x in range(x0, x1) if sum(blue(px(x, y)) for y in top) >= 20] +if cols: + ys = [y for y in top if sum(blue(px(x, y)) for x in cols[::3]) >= len(cols[::3]) // 2] + print("pill", (cols[0] + cols[-1]) // 2, (ys[0] + ys[-1]) // 2 if ys else int(sy + 60)) +out, y = [], int(sy + 60) +count = lambda y: sum(grey(px(x, y)) for x in range(x0, x1, 2)) * 2 +while y < int(min(sy + sh, h)): + if count(y) >= 0.4 * inner: + start = y + while y < h and count(y) >= 0.4 * inner: y += 1 + if y - start >= 20: + row, runs, run = start + 3, [], None + for x in range(x0, x1): + if grey(px(x, row)): + run = [x, x] if run is None else [run[0], x] + elif run: runs.append(run); run = None + if run: runs.append(run) + runs = [r for r in runs if r[1] - r[0] >= 0.3 * inner] + mid = (start + y) // 2 + if runs and runs[0][1] - runs[0][0] >= 0.85 * inner: + if not any(l.startswith("full") for l in out): + # The note line just above the first full-width control: + # how far right its text reaches (0: no text). + ink = [x for x in range(x0, x1) for yy in range(start - 40, start - 6) if max(px(x, yy)) < 120] + out.append("note %d" % (max(ink) - sx if ink else 0)) + out.append("full %d" % mid) + else: + for r in runs: out.append("half %d %d" % ((r[0] + r[1]) // 2, mid)) + y += 1 +print("\n".join(out)) +EOF +# `controls NAME RECT…` grabs, then prints the sheet's controls. +controls() { local png; png=$(grab "$1"); shift; python3 "$WORK/sheet.py" "$png" "$@"; } +# The import sheet moved on to the PIN once its note reads "Code read from +# the image. Type the PIN shown beside it." (wider than "Choose a screenshot +# or photo of the code…" or none), with no script error on the way. +pin_prompt() { + local C n + for _ in $(seq 1 10); do + sleep 1 + C=$(controls "$@") + n=$(echo "$C" | awk '$1=="note"{print $2; exit}') + [ "${n:-0}" -ge 420 ] && break + done + if grep -q "not found in prototype chain" "$LOG"; then + grep "not found in prototype chain" "$LOG" | head -3 + fail "the import sheet's script failed" + fi + [ "${n:-0}" -ge 420 ] || return 1 + echo "$C" +} +nth_full() { awk -v n="$1" '$1=="full"{i++; if(i==n){print $2; exit}}'; } +pill() { awk '$1=="pill"{print $2, $3; exit}'; } +# Blocks of one colour in a grab (the wizard's fields, rows, pull-downs and +# buttons, the cards' controls): "cx cy w h" per block, top to bottom. +# `blocks.py SHEET_PY PNG RRGGBB [MINCOUNT] [MINW]`: rows with at least +# MINCOUNT pixels of the colour (+-3; +-30 for text, MINCOUNT 1) make bands +# (a text band may pause for 3 rows); a band splits where the colour pauses +# for more than 12 px across (24 for text); narrower blocks than MINW drop. +cat >"$WORK/blocks.py" <<'EOF' +import sys +exec(open(sys.argv[1]).read().split("path, sx, sy")[0]) +w, h, bpp, rows = read_png(sys.argv[2]) +want = tuple(int(sys.argv[3][i:i + 2], 16) for i in (0, 2, 4)) +mincount = int(sys.argv[4]) if len(sys.argv) > 4 else 40 +minw = int(sys.argv[5]) if len(sys.argv) > 5 else 40 +text = mincount <= 1 +gap, tol = (24, 30) if text else (12, 3) +def hits(y): + r = rows[y] + return [x for x in range(w) if abs(r[x * bpp] - want[0]) <= tol and abs(r[x * bpp + 1] - want[1]) <= tol and abs(r[x * bpp + 2] - want[2]) <= tol] +bands, cur, quiet = [], None, 0 +for y in range(h): + xs = hits(y) + if len(xs) >= mincount: + if cur is None: cur = [y, y, set()] + cur[1] = y; cur[2].update(xs); quiet = 0 + elif cur is not None: + quiet += 1 + if quiet > (3 if text else 0): bands.append(cur); cur = None; quiet = 0 +if cur: bands.append(cur) +out = [] +for y0, y1, xs in bands: + xs = sorted(xs); start = prev = xs[0] + for x in xs[1:] + [10 ** 9]: + if x - prev > gap: + if prev - start + 1 >= minw and y1 - y0 + 1 >= (6 if text else 14): + out.append(((start + prev) // 2, (y0 + y1) // 2, prev - start + 1, y1 - y0 + 1)) + start = x + prev = x +for b in sorted(out, key=lambda b: (b[1], b[0])): print(*b) +EOF +# The wizard's and the cards' colours (bundle/main.splash and +# host-service/src/sheets.rs in OctoSense-System-Apps): pull-down, option +# row, chosen row, field, Test connection, Save without testing, accent, +# danger. +PULLDOWN=edf2fc; OPTION=f6f8fc; CHOSEN=e8f0fe; FIELD=f2f2f7; TEST=e3edff; BYPASS=fff1e0; BLUE=007aff; RED=ff3b30 +in_png() { python3 "$WORK/blocks.py" "$WORK/sheet.py" "$1" "$2" "${3:-40}" "${4:-40}"; } +# `blocks NAME COLOUR [MINCOUNT] [MINW]`: grab, then the colour's blocks. +blocks() { local png; png=$(grab "$1"); in_png "$png" "$2" "${3:-40}" "${4:-40}"; } +nth() { awk -v n="$1" 'NR==n{print $1, $2; exit}'; } +# The app scrolls as one list: its top (the cards) or its end (the QR +# buttons, under the cards). +top_app() { scroll 548 400 -3000; sleep 1; } +bottom_app() { scroll 548 400 3000; sleep 1; } + +profile_families() { + python3 -c ' +import json,sys +llm=json.load(open(sys.argv[1]))["config"]["llm"] +print(" ".join(s["family_id"] for s in [llm.get("primary")]+llm.get("fallbacks",[]) if s))' "$PROFILE" +} + +for _ in $(seq 1 60); do curl -s "127.0.0.1:$PORT/snap?q=main_window" | grep -q '"s":\[{' && break; sleep 0.5; done +pass "desktop up on port $PORT (pid $PID)" + +# 1. AI providers opens and offers the image import. +key Space "&cmd=1"; type_keys "ai providers"; key Return +wait_log "card: os.ai-providers running under" +wait_widget "Import code from image" +pass "AI providers opened; the host has an image picker (Import code from image)" + +# The rows are drawn by the app's script (on_render), which /snap does not +# list: the masked keys are in the grab; the keys themselves in the profile. +keys_end_with() { + python3 -c ' +import json,sys +env=json.load(open(sys.argv[1]))["config"]["env_vars"] +want=dict(a.split("=") for a in sys.argv[2:]) +sys.exit(0 if sorted(env)==sorted(want) and all(env[k].endswith(v) for k,v in want.items()) else 1)' "$PROFILE" "$@" +} +# Each saved route's model and the key env var octos reads it from. +profile_routes() { + python3 -c ' +import json,sys +llm=json.load(open(sys.argv[1]))["config"]["llm"] +print(" ".join("%s:%s" % (s.get("model_id","-"), (s.get("route") or {}).get("api_key_env","-")) for s in [llm.get("primary")]+llm.get("fallbacks",[]) if s))' "$PROFILE" +} +# The import sheet closes by itself once the code is imported. +sheet_closes() { + for _ in $(seq 1 40); do [ -z "$(sheet_rect "$1")" ] && return 0; sleep 0.5; done + return 1 +} + +# The add wizard, driven like a person. `next_step NAME` grabs (NAME) and +# presses the footer's Next (or the save on step 5): the lowest blue button +# narrower than a field; greyed out, it is not blue, and the step fails. +next_step() { + local X Y + read -r X Y _ < <(blocks "$1" $BLUE 40 60 | awk '$3<400' | tail -1) || true + [ -n "$Y" ] || fail "$1: Next is not enabled" + click "$X" "$Y"; sleep 1.5 +} +# `pick NAME N`: open the step's pull-down and take its option N. +pick() { + local X Y W H X2 Y2 + read -r X Y W H _ < <(blocks "$1-closed" $PULLDOWN 40 400 | head -1) || true + [ -n "$Y" ] || fail "$1: no pull-down on this step" + click "$X" "$Y"; sleep 1.5 + read -r X2 Y2 _ < <(blocks "$1" $OPTION 40 400 | awk -v y=$((Y + H / 2)) '$2>y' | nth "$2") || true + [ -n "$Y2" ] || fail "$1: the pull-down shows no option $2" + click "$X2" "$Y2"; sleep 1 +} +# `add_model NAME QUERY MODEL_N ROUTE_N KEY [BASE]`: the five steps. Step 1 +# searches the families for QUERY and chooses the first; steps 2 and 3 take +# option MODEL_N / ROUTE_N of their pull-downs (0: keep the preselected +# one), and BASE goes into the custom route's base URL; step 4 takes KEY; +# step 5 tests, then saves (Save once the test passes, else Save without +# testing after a network failure); SAVED says which: tested or untested. +add_model() { + local name=$1 query=$2 model_n=$3 route_n=$4 key=$5 base=${6:-} X Y before + SAVED= + before=$(cat "$PROFILE" 2>/dev/null || true) + read -r X Y _ < <(widget "Add model") || true; click "$X" "$Y" + read -r SX SY SW SH < <(wait_sheet "llm.sheet.fetch_models") + sleep 1.5 + read -r X Y _ < <(blocks "$name-1-start" $FIELD 40 400 | nth 1) || true + [ -n "$Y" ] || fail "$name: no search field on step 1" + click "$X" "$Y"; text "$query"; sleep 1.5 + read -r X Y _ < <(blocks "$name-1-search" $OPTION 40 400 | nth 1) || true + [ -n "$Y" ] || fail "$name: no family matches $query" + click "$X" "$Y"; sleep 1.5 + [ -n "$(blocks "$name-1-family" $CHOSEN 40 400)" ] || fail "$name: the family row is not marked chosen" + next_step "$name-1-next" + [ "$model_n" -gt 0 ] && pick "$name-2-model" "$model_n" + next_step "$name-2-next" + [ "$route_n" -gt 0 ] && pick "$name-3-route" "$route_n" + if [ -n "$base" ]; then + read -r X Y _ < <(blocks "$name-3-custom" $FIELD 40 400 | nth 1) || true + [ -n "$Y" ] || fail "$name: no base URL field" + click "$X" "$Y"; text "$base"; sleep 1 + fi + next_step "$name-3-next" + read -r X Y _ < <(blocks "$name-4-key" $FIELD 40 400 | nth 1) || true + [ -n "$Y" ] || fail "$name: no key field on step 4" + click "$X" "$Y"; text "$key"; sleep 1 + next_step "$name-4-next" + read -r X Y _ < <(blocks "$name-5-review" $TEST 40 400 | head -1) || true + [ -n "$Y" ] || fail "$name: no Test connection on step 5" + [ -z "$(blocks "$name-5-review" $BLUE 40 60 | awk '$3<400')" ] || fail "$name: Save is enabled before a test" + click "$X" "$Y" + for _ in $(seq 1 15); do + sleep 2 + read -r X Y _ < <(blocks "$name-5-tested" $BLUE 40 60 | awk '$3<400' | tail -1) || true + if [ -n "$Y" ]; then click "$X" "$Y"; SAVED=tested; break; fi + read -r X Y _ < <(in_png "$WORK/grabs/$name-5-tested.png" $BYPASS 40 100 | head -1) || true + if [ -n "$Y" ]; then click "$X" "$Y"; SAVED=untested; break; fi + done + [ -n "$Y" ] || fail "$name: the test neither passed nor offered Save without testing" + # The result line: green "Connected · N ms", or red "Couldn't connect: …". + if [ "$SAVED" = tested ]; then + [ -n "$(in_png "$WORK/grabs/$name-5-tested.png" 248a3d 1 100)" ] || fail "$name: no green Connected line (grab $name-5-tested)" + else + [ -n "$(in_png "$WORK/grabs/$name-5-tested.png" c4281c 1 100)" ] || fail "$name: no red Couldn't connect line (grab $name-5-tested)" + fi + for _ in $(seq 1 20); do [ "$(cat "$PROFILE" 2>/dev/null || true)" != "$before" ] && return 0; sleep 0.5; done + fail "$name: the save did not change the profile" +} +profile_json() { python3 -c 'import json,sys; print(json.dumps(json.load(open(sys.argv[1]))["config"].get(sys.argv[2]), sort_keys=True))' "$PROFILE" "$1"; } + +# W1. DeepSeek with the wizard; the provider is unreachable (sandbox). +add_model w1 deepseek 1 1 "$FAKE_KEY" +[ "$SAVED" = untested ] || fail "DeepSeek was not saved without testing" +wait_widget "Added DeepSeek · deepseek-v4-flash" +sheet_closes "llm.sheet.fetch_models" || fail "the wizard stayed up after the save" +[ "$(profile_json llm)" = '{"fallbacks": [], "primary": {"family_id": "deepseek", "model_id": "deepseek-v4-flash"}}' ] \ + || fail "profile after the wizard: $(profile_json llm)" +keys_end_with DEEPSEEK_API_KEY=1234 || fail "the DeepSeek key is not in the profile" +pass "DeepSeek added with the wizard: family search, model pull-down (DeepSeek V4 Flash, Recommended), route pull-down (Official API), key, Test connection failed (unreachable), Save without testing (grabs w1-1-family, w1-2-model, w1-3-route, w1-4-next, w1-5-review, w1-5-tested)" + +# W2. The card's model pull-down. +read -r X Y _ < <(blocks w2-list $PULLDOWN 40 400 | nth 1) || true +[ -n "$Y" ] || fail "no model pull-down on the DeepSeek card" +click "$X" "$Y"; sleep 2 +read -r X Y _ < <(blocks w2-pulldown $OPTION 40 400 | nth 2) || true +[ -n "$Y" ] || fail "the card's model pull-down has no second model" +click "$X" "$Y" +wait_widget "DeepSeek now uses DeepSeek V4 Pro." +[ "$(profile_json llm)" = '{"fallbacks": [], "primary": {"family_id": "deepseek", "model_id": "deepseek-v4-pro"}}' ] \ + || fail "profile after the model change: $(profile_json llm)" +pass "the card's model pull-down listed DeepSeek's catalog models and switched to DeepSeek V4 Pro (grab w2-pulldown)" + +# W3. Z.ai as a fallback: GLM-4.7 (the second model) on a custom +# OpenAI-compatible route (the route pull-down's option after Official API), +# the local fake endpoint: the test passes. +FAKE_PORT=$(head -1 "$WORK/fake.port") +[ -n "$FAKE_PORT" ] || fail "the fake endpoint did not start" +ZAI_BASE="http://127.0.0.1:$FAKE_PORT/v1" +add_model w3 "z.ai" 2 2 "$ZAI_KEY" "$ZAI_BASE" +[ "$SAVED" = tested ] || fail "Z.ai was not saved after a passing test" +wait_widget "Added Z.ai · glm-4.7" +sheet_closes "llm.sheet.fetch_models" || fail "the wizard stayed up after the Z.ai save" +[ "$(profile_json llm)" = '{"fallbacks": [{"family_id": "zai", "model_id": "glm-4.7", "route": {"api_type": "openai", "base_url": "'"$ZAI_BASE"'"}}], "primary": {"family_id": "deepseek", "model_id": "deepseek-v4-pro"}}' ] \ + || fail "profile after the Z.ai add: $(profile_json llm)" +keys_end_with DEEPSEEK_API_KEY=1234 ZAI_API_KEY=5678 || fail "the keys after the Z.ai add are wrong" +pass "Z.ai added as a fallback on GLM-4.7 and a custom OpenAI-compatible route; Test connection passed, Add as fallback saved it (grabs w3-2-model, w3-3-custom, w3-5-tested)" + +# W4. The list shows both; Test connection on the fallback's row passes; +# remove both with the cards' Remove (tap twice). +# Both cards in view: the list scrolled past the header. +top_app; scroll 548 400 220; sleep 1 +[ "$(blocks w4-saved-models $PULLDOWN 40 400 | wc -l | tr -d ' ')" = 2 ] || fail "the list does not show two model pull-downs (grab w4-saved-models)" +# Green text: the rows' key statuses, and "Connected" once the test passes. +GREEN=248a3d +read -r X Y _ < <(blocks w4-rows $TEST 40 60 | tail -1) || true +[ -n "$Y" ] || fail "no Test connection on the second row" +BEFORE=$(in_png "$WORK/grabs/w4-rows.png" $GREEN 1 60 | wc -l) +click "$X" "$Y" +for _ in $(seq 1 20); do sleep 1; [ "$(blocks w4-row-tested $GREEN 1 60 | wc -l)" -gt "$BEFORE" ] && break; done +[ "$(in_png "$WORK/grabs/w4-row-tested.png" $GREEN 1 60 | wc -l)" -gt "$BEFORE" ] || fail "the row's Test connection did not say Connected (grab w4-row-tested)" +for n in 1 2; do + top_app + read -r X Y _ < <(blocks "w4-remove-$n" $RED 1 30 | head -1) || true + [ -n "$Y" ] || fail "no Remove on the first card (grab w4-remove-$n)" + click "$X" "$Y"; sleep 1 + click "$X" "$Y"; sleep 2 +done +python3 -c 'import json,sys; sys.exit(0 if "llm" not in json.load(open(sys.argv[1]))["config"] else 1)' "$PROFILE" || fail "the list is not empty after removing both: $(profile_json llm)" +top_app; grab w4-empty >/dev/null +pass "the list showed DeepSeek V4 Pro (primary) and Z.ai GLM-4.7 (fallback 1); the fallback's Test connection said Connected; both removed with Remove, tapped twice (grabs w4-saved-models, w4-row-tested, w4-empty)" + +# 2. Import into the empty list by pasting an old single-provider code. +bottom_app; read -r X Y < <(widget "Import code from image"); click "$X" "$Y" +read -r SX SY SW SH < <(wait_sheet "llm.sheet.import") +sleep 1 +C=$(controls paste-sheet "$SX" "$SY" "$SW" "$SH") +CODE_Y=$(echo "$C" | nth_full 2); read -r PX PY < <(echo "$C" | pill) +[ -n "$CODE_Y" ] && [ -n "$PX" ] || { echo "$C"; fail "import sheet controls not found"; } +click $((SX + SW / 2)) "$CODE_Y"; text "$LEGACY_CODE"; click "$PX" "$PY" +wait_widget "Saved DeepSeek · deepseek-v4-flash as primary." +sheet_closes "llm.sheet.import" || fail "the import sheet stayed up after the paste" +[ "$(profile_routes)" = "deepseek-v4-flash:-" ] || fail "profile after the paste: $(profile_routes)" +keys_end_with DEEPSEEK_API_KEY=fcb0 || fail "the pasted key is not in the profile" +grab imported-by-paste >/dev/null +pass "an old single-provider code pasted into the empty list: DeepSeek deepseek-v4-flash is the primary (grab imported-by-paste)" + +# 3. Add OpenAI with a fake key on the host's sheet. +add_openai() { + add_model "$1" openai 0 0 "$FAKE_KEY" + [ "$SAVED" = untested ] || fail "OpenAI was not saved without testing" + wait_widget "Added OpenAI · gpt-4o" +} +add_openai add1 +[ "$(profile_families)" = "deepseek openai" ] || fail "profile after the add: $(profile_families)" +keys_end_with DEEPSEEK_API_KEY=fcb0 OPENAI_API_KEY=1234 || fail "OpenAI's key is not in the profile" +grab added-openai >/dev/null +pass "OpenAI added with the wizard (gpt-4o, official route), saved without testing (grab added-openai)" + +# 4. Import QR-A by drop onto the sheet: appended, no confirm step. +bottom_app; read -r X Y < <(widget "Import code from image"); click "$X" "$Y" +read -r SX SY SW SH < <(wait_sheet "llm.sheet.image") +sleep 1 +DROP=$(curl -s --get --data-urlencode "path=$FIXTURE" --data-urlencode "x=$((SX + SW / 2))" \ + --data-urlencode "y=$((SY + SH / 2))" --data-urlencode wait=1 "127.0.0.1:$PORT/drop") +case $DROP in *'"drop_handled":true'*'"drag_response":"copy"'*) ;; *) fail "the sheet did not take the drop: $DROP" ;; esac +wait_log "llm: image dropped on the import sheet" +C=$(pin_prompt drop-read "$SX" "$SY" "$SW" "$SH") || fail "the sheet did not ask for the PIN after the drop" +PIN_Y=$(echo "$C" | nth_full 3); read -r PX PY < <(echo "$C" | pill) +[ -n "$PIN_Y" ] && [ -n "$PX" ] || { echo "$C"; fail "import sheet controls not found"; } +click $((SX + SW / 2)) "$PIN_Y"; text "$FIXTURE_PIN"; click "$PX" "$PY" +wait_widget "Added DeepSeek · deepseek-chat, Z.ai · glm-4.6 as fallbacks." +sheet_closes "llm.sheet.image" || fail "the import sheet stayed up (a confirm step?)" +[ "$(profile_families)" = "deepseek openai deepseek zai" ] || fail "profile after the drop: $(profile_families)" +[ "$(profile_routes)" = "deepseek-v4-flash:- gpt-4o:- deepseek-chat:DEEPSEEK_2_API_KEY glm-4.6:-" ] || fail "routes after the drop: $(profile_routes)" +keys_end_with DEEPSEEK_API_KEY=fcb0 OPENAI_API_KEY=1234 DEEPSEEK_2_API_KEY=0000 ZAI_API_KEY=0000 \ + || fail "the keys after the drop are wrong (the primary's key must be untouched)" +grab imported-by-drop >/dev/null +pass "dropped QR-A appended as fallbacks without a confirm step: primary + OpenAI first, QR-A's DeepSeek in DEEPSEEK_2_API_KEY, the primary's key untouched (grab imported-by-drop)" + +# 5. Import QR-A again by the picker (test image), a wrong PIN first: the same +# code again changes nothing. +pick_and_unlock() { + local X Y C + bottom_app; read -r X Y < <(widget "Import code from image"); click "$X" "$Y" + read -r SX SY SW SH < <(wait_sheet "llm.sheet.pick") + sleep 1 + C=$(controls "$1-sheet" "$SX" "$SY" "$SW" "$SH") + CHOOSE_Y=$(echo "$C" | nth_full 1); PIN_Y=$(echo "$C" | nth_full 3); read -r PX PY < <(echo "$C" | pill) + click $((SX + SW / 2)) "$CHOOSE_Y" + pin_prompt "$1-picked" "$SX" "$SY" "$SW" "$SH" >/dev/null || fail "the sheet did not ask for the PIN after the pick" +} +pick_and_unlock pick1 +BEFORE=$(cat "$PROFILE") +click $((SX + SW / 2)) "$PIN_Y"; text "0000-0000"; click "$PX" "$PY"; sleep 4 +[ -n "$(sheet_rect "llm.sheet.pick")" ] || fail "a wrong PIN closed the sheet" +[ "$(cat "$PROFILE")" = "$BEFORE" ] || fail "a wrong PIN changed the profile" +grab wrong-pin >/dev/null +click $((SX + SW / 2)) "$PIN_Y" +for _ in 1 2 3 4 5 6 7 8 9; do key Backspace; done +text "$FIXTURE_PIN"; click "$PX" "$PY" +wait_widget "Already saved: DeepSeek · deepseek-chat, Z.ai · glm-4.6." +sheet_closes "llm.sheet.pick" || fail "the import sheet stayed up after the second import" +[ "$(cat "$PROFILE")" = "$BEFORE" ] || fail "importing QR-A again changed the profile" +grab reimported >/dev/null +pass "QR-A picked and imported again after a refused wrong PIN: nothing changed, no duplicates (grab reimported)" + +# 6. The phone QR: show the code, read it from the grab. +LOG_MARK=$(( $(wc -l <"$LOG") + 1 )) +bottom_app; read -r X Y < <(widget "Show QR for phone"); click "$X" "$Y" +read -r SX SY SW SH < <(wait_sheet "countdown :=") +SHOWN=$(date +%s) +sleep 1 +PIN=$(get "snap?q=$(q "pin := Label")" | python3 -c ' +import json,re,sys +for e in json.load(sys.stdin)["s"]: + m=re.search(r"pin := Label\{[^\n]*text: \"([A-Z0-9-]+)\"", e.get("t") or "") + if m: print(m.group(1))' | tail -1) +[ -n "$PIN" ] || fail "no PIN on the export sheet" +EXPORT=$(grab export 1) +cp "$EXPORT" "$WORK/export.png"; echo "$PIN" >"$WORK/export-pin.txt" +READ=$("$READ_QR" "$EXPORT" "$PIN" "$WORK/octos") || fail "the exported QR does not match the profile: $READ" +echo "$READ" | grep -q '"same_as_profile":true' || fail "$READ" +echo "$READ" | grep -q '"family":"openai"' || fail "OpenAI missing from the code: $READ" +echo "$READ" | grep -q '"DEEPSEEK_2_API_KEY":"••••0000"' || fail "the second DeepSeek slot is missing from the code: $READ" +echo "$READ" | grep -q '"DEEPSEEK_API_KEY":"••••fcb0"' || fail "the primary's key is missing from the code: $READ" +pass "export QR read back from the grab (rqrr) with PIN $PIN: same set and keys as _main.json, both DeepSeek slots included" +# The countdown ticks: two grabs of the sheet two seconds apart differ (only +# the "Expires in" line, below the PIN, changes on it: scroll down to it). +same_sheet() { + python3 - "$1" "$2" "$SX" "$SY" "$SW" "$SH" "$WORK/sheet.py" <<'PY' +import sys +exec(open(sys.argv[7]).read().split("path, sx, sy")[0]) +a, b = read_png(sys.argv[1]), read_png(sys.argv[2]) +sx, sy, sw, sh = map(int, map(float, sys.argv[3:7])) +rows = range(sy, min(sy + sh, a[1], b[1])) +diff = sum(a[3][y][sx * a[2]:(sx + sw) * a[2]] != b[3][y][sx * b[2]:(sx + sw) * b[2]] for y in rows) +sys.exit(0 if diff == 0 else 1) +PY +} +scroll $((SX + SW / 2)) $((SY + SH / 2)) 600; sleep 1 +T1=$(grab countdown-1); sleep 2; T2=$(grab countdown-2) +same_sheet "$T1" "$T2" && fail "the countdown does not tick (grabs countdown-1 and countdown-2 are the same)" +# At 0 the sheet closes itself: the service's own close comes 5 s after the +# lifetime (counted from before the code was sealed), so a sheet gone within +# the lifetime + 2 s closed itself. +for _ in $(seq 1 $((QR_SECONDS * 2 + 20))); do [ -z "$(sheet_rect "countdown :=")" ] && break; sleep 0.5; done +CLOSED=$(( $(date +%s) - SHOWN )) +[ -z "$(sheet_rect "countdown :=")" ] || fail "the export sheet did not close after $QR_SECONDS s" +[ "$CLOSED" -le $((QR_SECONDS + 2)) ] || fail "the export sheet closed after ${CLOSED} s, not by its own countdown ($QR_SECONDS s)" +grab export-closed >/dev/null +if tail -n +"$LOG_MARK" "$LOG" | grep -Eq "$SPLASH_ERRORS"; then + tail -n +"$LOG_MARK" "$LOG" | grep -E "$SPLASH_ERRORS" | head -5 + fail "Splash errors while the export sheet was up" +fi +pass "the countdown ticked and the sheet closed itself after ${CLOSED} s ($QR_SECONDS s lifetime); no Splash error during the export" + +# 7. No key text anywhere the UI or logs show; no panic. +get "snap?all=1" >"$WORK/snap.json"; get "d" >"$WORK/dump.txt"; get "log?n=5000" >"$WORK/remote-log.json" +if grep -Eq "$SECRETS" "$WORK/snap.json" "$WORK/dump.txt" "$WORK/remote-log.json" "$LOG"; then + grep -Eo ".{40}($SECRETS).{10}" "$WORK/snap.json" "$WORK/dump.txt" "$WORK/remote-log.json" "$LOG" | head -5 + fail "key text in the UI tree or a log" +fi +grep -q "panicked" "$LOG" "$WORK/remote-log.json" && fail "panic in the log" +if grep -Eq "$SPLASH_ERRORS" "$LOG"; then grep -E "$SPLASH_ERRORS" "$LOG" | head -5; fail "Splash errors in the host log"; fi +pass "no key text in /snap, /d, /log or the host log; no panic, no Splash error" + +get "gq?scale=0.5" >/dev/null +for _ in $(seq 1 30); do kill -0 "$PID" 2>/dev/null || break; sleep 0.5; done +kill -0 "$PID" 2>/dev/null && fail "the shell did not exit after /gq" +kill "$FAKE" 2>/dev/null || true +trap - EXIT +pass "shell exited after /gq; grabs in $WORK/grabs, export.png + export-pin.txt in $WORK" diff --git a/scripts/system_apps_remote.sh b/scripts/system_apps_remote.sh new file mode 100755 index 00000000..b4e4f198 --- /dev/null +++ b/scripts/system_apps_remote.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# The system apps on the desktop, driven through Makepad's remote +# (instrument) mode: no OS screenshots, only the app's own frames. +# +# cargo build --release [--config …] && scripts/system_apps_remote.sh [artifacts-dir] +# +# Runs the release binary hidden, with its own OCTOSENSE_HOME, octos core dir +# and file vaults under a temp directory, so neither ~/.octosense nor the login +# keychain is touched. Checks: the launcher lists every selected system app; +# Photos opens and draws (no network needed); Mail opens and its host-owned +# sign-in sheet appears (nobody signs in); each app runs in an isolate under +# its manifest's policy; bundles unpack under /apps/.system//; +# AI providers (when selected) opens on its empty provider list; the log has +# no panic and no UI hang outside the known ones. Grabs are kept as evidence. +set -euo pipefail + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +BIN=${OCTOSENSE_BIN:-$ROOT/target/release/octosense} +WORK=${1:-$(mktemp -d -t octosense-system-apps)} +mkdir -p "$WORK/home" "$WORK/octos" "$WORK/grabs" +LOG=$WORK/host.log +APPS=$(python3 -c 'import json,sys; print(" ".join(json.load(open(sys.argv[1]))["apps"]))' "$ROOT/system-apps.json") +echo "artifacts: $WORK" +echo "system apps: $APPS" + +# From the temp directory: Maps' tile cache is written relative to the +# working directory (./local/tilecache_v4), not under the app's jail. +cd "$WORK" +env -u MAKEPAD_HOME -u MAKEPAD_WM_ROOT -u MAKEPAD_WM_THEME \ + OCTOSENSE_HOME="$WORK/home" OCTOS_APP_CORE_DIR="$WORK/octos" \ + OCTOSENSE_MAIL_VAULT=file OCTOSENSE_LLM_VAULT=file \ + MAKEPAD_HIDE_WINDOWS=1 "$BIN" --remote >"$LOG" 2>&1 & +PID=$! +PORT= +cleanup() { + if kill -0 "$PID" 2>/dev/null; then + [ -n "$PORT" ] && curl -s "127.0.0.1:$PORT/quit" >/dev/null || true + sleep 2 + kill "$PID" 2>/dev/null || true + fi +} +trap cleanup EXIT + +fail() { echo "FAIL: $*"; exit 1; } +pass() { echo "PASS: $*"; } +for _ in $(seq 1 120); do + PORT=$(sed -n 's/.*\[makepad-remote\] listening on 127\.0\.0\.1:\([0-9]*\) pid=.*/\1/p' "$LOG" | head -1) + [ -n "$PORT" ] && break + sleep 0.5 +done +[ -n "$PORT" ] || fail "no remote port in $LOG" +get() { curl -fsS "127.0.0.1:$PORT/$1"; } +# Input waits for the next frame; while a bundle unpacks on the UI thread the +# wait can time out after the key was delivered. What a key did is checked +# through the log and snapshots, so a timed-out wait is only noted. +key() { + local out + out=$(curl -s "127.0.0.1:$PORT/k?k=press&c=$1&wait=1${2:-}") + case $out in *'"err"'*) echo "note: key $1: $out" ;; esac +} +grab() { + local png + for _ in 1 2 3 4 5; do + # A hidden window's grab can miss its frame ("retry"): ask again. + png=$(curl -s "127.0.0.1:$PORT/g?scale=0.5" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("png",""))' || true) + [ -n "$png" ] && { cp "$png" "$WORK/grabs/$1.png"; return; } + sleep 1 + done + fail "no frame for $1" +} +# `/snap` texts, one per line, for a query. +snap() { get "snap?q=$(python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$1")" \ + | python3 -c 'import json,sys; [print(e["i"], e["ty"]) for e in json.load(sys.stdin)["s"]]'; } +wait_log() { + for _ in $(seq 1 ${2:-40}); do grep -q "$1" "$LOG" && return 0; sleep 0.5; done + fail "log never said: $1" +} +# Type into the Start menu's search (KeyDown characters, as the smoke test does). +type_keys() { + local text=$1 i c + for ((i = 0; i < ${#text}; i++)); do + c=${text:i:1} + case $c in + " ") key Space ;; + -) key Minus ;; + *) key "Key$(printf %s "$c" | tr a-z A-Z)" ;; + esac + done +} +launch() { key Space "&cmd=1"; type_keys "$1"; key Return; } +label_of() { # the manifest name of a selected app + python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["name"])' \ + "$ROOT/$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["source"])' "$ROOT/system-apps.json")/$1/bundle/manifest.json" +} + +for _ in $(seq 1 60); do curl -s "127.0.0.1:$PORT/snap?q=main_window" | grep -q '"s":\[{' && break; sleep 0.5; done +pass "desktop up on port $PORT (pid $PID)" + +# 1. The launcher lists each system app (its short id, its manifest name). +for app in $APPS; do + key Space "&cmd=1" + type_keys "$app" + grab "launcher-$app" + key Escape +done +python3 - "$WORK/grabs" $APPS <<'EOF' || fail "launcher grabs missing" +import sys, pathlib +d = pathlib.Path(sys.argv[1]) +assert all((d / f"launcher-{a}.png").stat().st_size > 10000 for a in sys.argv[2:]) +EOF +grep -q 'wm: modules linked: .*"card"' "$LOG" || fail "the Card runner is not linked" +pass "launcher searched for: $APPS (grabs launcher-*.png); Card runner linked" + +# 2. Photos: opens in the Card runner and draws its library. +launch photos +wait_log "card: os.photos running under" +for _ in $(seq 1 20); do snap "Collections" | grep -q "^card Splash" && break; sleep 0.5; done +snap "Collections" | grep -q "^card Splash" || fail "Photos drew nothing" +grab photos +pass "Photos opened: $(grep -o 'card: os.photos running under.*' "$LOG" | head -1)" + +# 3. Mail: opens, and its sign-in is the host's sheet, not the app's. +launch mail +wait_log "card: os.mail running under" +sleep 2 +grab mail-welcome +snap "Add account" | grep -q "^card Splash" || fail "Mail shows no Add account" +# The button is the Splash's; click its centre as drawn in the welcome pane. +read -r X Y < <(get "snap?q=card" | python3 -c ' +import json,sys +cards=[e for e in json.load(sys.stdin)["s"] if e["i"]=="card" and "account = nil" in (e.get("t") or "")] +x,y,w,h=cards[-1]["r"]; print(x+w/2, y+h*0.567)') +get "click?x=$X&y=$Y&wait=1" >/dev/null +for _ in $(seq 1 20); do snap "Add a mail account" | grep -q "^sheet Splash" && break; sleep 0.5; done +snap "Add a mail account" | grep -q "^sheet Splash" || fail "Mail's sign-in sheet did not open" +grab mail-sheet +pass "Mail's host-owned sign-in sheet is up (not signed in)" + +# 4. AI providers, when this build selects it: opens on an empty list. +if [[ " $APPS " == *" ai-providers "* ]]; then + key Escape + launch "ai providers" + sleep 3 + if grep -q 'os.ai-providers requests unknown capability' "$LOG"; then + fail "App Hub's policy at this pin does not know the \`llm\` capability; AI providers is refused (its notice is drawn)" + fi + wait_log "card: os.ai-providers running under" + sleep 2 + grab ai-providers + snap "No models yet" | grep -q "^card Splash" || fail "AI providers shows no empty list" + NAME=$(label_of ai-providers) + [ -z "$(ls -A "$WORK/octos" 2>/dev/null)" ] || echo "note: octos core dir is not empty" + pass "$NAME opened: $(grep -o 'card: os.ai-providers running under.*' "$LOG" | head -1)" +fi + +# 5. Containment: one isolate per app under its policy, `os.` ids, bundles +# unpacked outside every jail under the temp home. +for app in photos mail; do + [ -f "$WORK/home/apps/.system/os.$app/"*/manifest.json ] || fail "os.$app not unpacked under $WORK/home/apps/.system" + [ -d "$WORK/home/apps/os.$app" ] || fail "os.$app has no jail" +done +pass "bundles unpacked under /apps/.system///, jails at /apps/" + +# 6. The log: no panic; UI hangs only where known (see KNOWN below). +get "log?n=2000" >"$WORK/remote-log.json" +grep -q "panicked" "$LOG" "$WORK/remote-log.json" && fail "panic in the log" +# Known: the catalog's startup Makepad-source probe, and App Hub unpacking and +# hashing a system bundle, or building an app's Splash VM, on the UI thread +# the first time it opens. +KNOWN='makepad_source::makepad_root|octosense_appstore::system::prepare|CxSplashVmExt>::alloc_splash_vm' +UNKNOWN=$(grep "\[ui-hang\]" "$LOG" | grep -Ev "$KNOWN" || true) +[ -z "$UNKNOWN" ] || { echo "$UNKNOWN" | cut -c1-400; fail "unexpected UI hang"; } +echo "note: $(grep -c "\[ui-hang\]" "$LOG" || true) known UI hang(s) (startup catalog probe, first-open bundle unpack or VM)" +pass "no panic, no unexpected [ui-hang]" + +# Finish: grab and quit, then confirm the process is gone. +get "gq?scale=0.5" >/dev/null +for _ in $(seq 1 30); do kill -0 "$PID" 2>/dev/null || break; sleep 0.5; done +kill -0 "$PID" 2>/dev/null && fail "the shell did not exit after /gq" +trap - EXIT +pass "shell exited after /gq; grabs in $WORK/grabs" diff --git a/src/ai_bus.rs b/src/ai_bus.rs index 50f7eff7..819f9702 100644 --- a/src/ai_bus.rs +++ b/src/ai_bus.rs @@ -227,8 +227,13 @@ impl AiBus { let Some(mut up) = HostedUp::parse(json) else { return Route::Drop }; // The sender's claim is never used: the link IS the identity. up.from = Some(Self::endpoint_of(client)); - match &up.msg { + match &mut up.msg { ServiceUp::Register { manifest, .. } => { + // Another process cannot vouch for the person's consent: its + // destructive tools wait for the pane's own confirm card. Only + // in-process modules (`register_local`, trusted native code) + // keep a tool's claim that its own sheet confirms it. + manifest.clear_self_confirm(); self.manifests.insert(client, manifest.clone()); } ServiceUp::Unregister => { @@ -497,4 +502,92 @@ mod local_tests { assert!(bus.local_clients().is_empty()); assert!(bus.client_died(4).is_none()); } + + #[test] + fn only_an_in_process_module_keeps_a_self_confirmed_tool() { + let send = || { + sheets().with_tool( + ToolDef::new("send", "Send the sheet.", r#"{"type":"object","properties":{}}"#, Risk::Destructive) + .confirmed_by_app(), + ) + }; + let registered = |json: &str| match HostedUp::parse(json).expect("valid").msg { + ServiceUp::Register { manifest, .. } => manifest, + _ => panic!("expected a registration"), + }; + let mut bus = AiBus { pane_client: Some(9), ..Default::default() }; + // A module in this process: its own sheet is the one confirmation. + let local = registered(&bus.register_local(4, send())); + assert!(local.tool("send").unwrap().confirms_itself()); + // A process client's claim is dropped before the pane sees it, and in + // the replay, so the pane confirms its destructive tools itself. + let up = HostedUp { from: None, msg: ServiceUp::Register { manifest: send(), port_tag: 0 } }; + let Route::ToPane(json) = bus.on_custom(5, &up.to_json()) else { panic!("expected ToPane") }; + assert!(!registered(&json).tool("send").unwrap().confirms_itself()); + let replay = bus.replay(AiBus::os_manifest(&[])); + assert!(registered(&replay[1]).tool("send").unwrap().confirms_itself(), "m4 keeps it"); + assert!(!registered(&replay[2]).tool("send").unwrap().confirms_itself(), "w5 does not"); + } + + /// End to end through the pane's engine: a module's self-confirmed tool + /// reaches the module at once (its own sheet asks the person), while the + /// same claim from another process still gets the pane's confirm card. + #[test] + fn the_pane_skips_its_confirm_only_for_a_modules_self_confirmed_tool() { + use makepad_ai_services::engine::{EngineCore, EngineEvent, Model, ModelEvent, ServiceRegistry, ToolDefinition}; + use makepad_ai_services::port::ServiceLink; + /// A model that asks for one call. + struct OneCall(Vec); + impl Model for OneCall { + fn label(&self) -> String { + "test".into() + } + fn configure(&mut self, _: &str, _: &[ToolDefinition]) -> Result<(), String> { + Ok(()) + } + fn send_user(&mut self, _: &str, _: &str) {} + fn send_tool_result(&mut self, _: &str, _: &str, _: bool) {} + fn cancel(&mut self) {} + fn reset(&mut self) {} + fn poll(&mut self) -> Vec { + std::mem::take(&mut self.0) + } + } + let manifest = || { + sheets() + .with_tool( + ToolDef::new("send", "Send the sheet.", r#"{"type":"object","properties":{}}"#, Risk::Destructive) + .confirmed_by_app(), + ) + .with_tool(ToolDef::new("delete", "Delete the sheet.", r#"{"type":"object","properties":{}}"#, Risk::Destructive)) + }; + let mut bus = AiBus { pane_client: Some(9), ..Default::default() }; + let local = bus.register_local(4, manifest()); + let up = HostedUp { from: None, msg: ServiceUp::Register { manifest: manifest(), port_tag: 0 } }; + let Route::ToPane(process) = bus.on_custom(5, &up.to_json()) else { panic!("expected ToPane") }; + for (frame, endpoint, tool, pane_confirms) in [ + (&local, "m4", "send", false), + (&local, "m4", "delete", true), + (&process, "w5", "send", true), + ] { + let up = HostedUp::parse(frame).expect("valid registration"); + let ServiceUp::Register { manifest, .. } = up.msg.clone() else { panic!("expected a registration") }; + let registry = ServiceRegistry::new(); + let (link, host) = ServiceLink::pair(manifest); + registry.register_as(link, EndpointId(endpoint.into()), "test", None).unwrap(); + host.up.send(up).unwrap(); + registry.pump(); + while host.down.try_recv().is_ok() {} + let call = ModelEvent::ToolCall { call_id: "m1".into(), name: format!("sheets.{tool}"), args: "{}".into() }; + let model = OneCall(vec![call, ModelEvent::TurnDone { tool_calls: 1 }]); + let mut core = EngineCore::new(registry, Box::new(model), None, 1); + core.send("do it", 0.0); + let events = core.pump(0.1); + let confirm = events.contains(&EngineEvent::Confirm { call_id: "m1".into() }); + assert_eq!(confirm, pane_confirms, "{endpoint} {tool}: {events:?}"); + let reached = std::iter::from_fn(|| host.down.try_recv().ok()) + .any(|down| matches!(down.msg, ServiceDown::Call(ref c) if c.tool == tool)); + assert_eq!(reached, !pane_confirms, "{endpoint} {tool}: the call reaches the app only without a card"); + } + } } diff --git a/src/app_peers_host.rs b/src/app_peers_host.rs new file mode 100644 index 00000000..dc698a94 --- /dev/null +++ b/src/app_peers_host.rs @@ -0,0 +1,170 @@ +//! Apps' assistant access (Rinx ADR 0007): the shell side of +//! `octosense-app-peers`. +//! +//! When the window manager creates a native module instance, the shell reads the +//! module's declared capabilities. If host policy grants any of the exact +//! `octos.*` assistant services, Home creates a scoped service for that +//! instance, backed by ONE octos peer per app and account. The peer is owned +//! by the shell's system agent (`_main:api:octosense#system`) and runs on the +//! shell's kernel (`octosense_octos_core`). The service is offered to the +//! instance for the duration of its `create` only. A module that declares or +//! is granted nothing gets no service, and no peer is ever allocated for it. +//! Tearing the instance down releases its leases and interrupts its peer's +//! running work. The kernel and other apps keep running. +//! +//! Host policy: native modules that ship with the shell are granted what they +//! declare from this list. The person's AI provider choice lives in +//! Settings → Accounts → AI providers; a per-app toggle is future work. + +use makepad_app_module::{AppModule, InstanceScope}; + +/// The native modules the shell lets use the assistant, and with what. +#[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] +pub fn policy() -> &'static octosense_app_peers::hosted::HostPolicy { + static POLICY: std::sync::OnceLock = + std::sync::OnceLock::new(); + POLICY.get_or_init(|| { + let policy = octosense_app_peers::hosted::HostPolicy::default(); + // Rinx: its native mini-app host serves these to reviewed mini apps. + policy.allow("rinx", octosense_app_peers::OCTOS_SERVICES); + policy + }) +} + +/// One instance's assistant service, held by the module host. +pub struct Assistant { + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + broker: octosense_app_peers::broker::Broker, +} + +impl Assistant { + /// The instance is going away: release its leases and contexts. + pub fn release(&self) { + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + octosense_app_peers::OctosAppService::release(&self.broker); + } +} + +/// Before `module.create`: offer the instance its service, if it gets one. +pub fn offer(module: &dyn AppModule, scope: &InstanceScope) -> Option { + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + { + let broker = octosense_app_peers::hosted::launch( + module.id(), + module.label(), + module.capabilities().iter().copied(), + policy(), + )?; + octosense_app_peers::hosted::offer(module.id(), &scope.to_string(), &broker); + Some(Assistant { broker }) + } + #[cfg(not(any(feature = "octos-core", target_os = "android", target_os = "ios")))] + { + let _ = (module, scope); + None + } +} + +/// After `module.create`: drop an offer the module did not take. +pub fn withdraw(module: &dyn AppModule, scope: &InstanceScope) { + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + octosense_app_peers::injection::withdraw(module.id(), &scope.to_string()); + #[cfg(not(any(feature = "octos-core", target_os = "android", target_os = "ios")))] + let _ = (module, scope); +} + +/// Rinx runs one instance per process: tests that create it take turns. +#[cfg(test)] +pub(crate) static RINX_INSTANCE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + +#[cfg(all(test, any(feature = "octos-core", target_os = "android", target_os = "ios")))] +mod tests { + use super::*; + use makepad_app_module::*; + use makepad_widgets::*; + use std::sync::atomic::{AtomicBool, Ordering}; + + static CLAIMED: AtomicBool = AtomicBool::new(false); + + /// A module that declares the assistant and records whether it got one. + struct Probe(&'static str, &'static [&'static str]); + impl AppModule for Probe { + fn id(&self) -> &'static str { + self.0 + } + fn label(&self) -> &'static str { + "Probe" + } + fn capabilities(&self) -> &'static [&'static str] { + self.1 + } + fn open_schema(&self) -> OpenSchema { + OpenSchema::new(1) + } + fn register(&self, _vm: &mut ScriptVm) {} + fn create(&self, vm: &mut ScriptVm, _open: ValidatedOpen, handles: InstanceHandles) -> InstanceParts { + let service = octosense_app_peers::injection::claim(self.0, &handles.scope.to_string()); + CLAIMED.store(service.is_some(), Ordering::SeqCst); + let value = script_eval!(vm, { use mod.prelude.widgets.* View {} }); + InstanceParts { + root: WidgetRef::script_from_value(vm, value), + executor: Box::new(NoExecutor), + shutdown: Box::new(|_| {}), + } + } + } + struct NoExecutor; + impl ServiceExecutor for NoExecutor { + fn manifest(&self) -> makepad_ai_services::wire::ServiceManifest { + makepad_ai_services::wire::ServiceManifest::new("probe", "Probe", "test") + } + fn execute(&mut self, _cx: &mut Cx, call: &makepad_ai_services::wire::ServiceCall) -> ExecOutcome { + ExecOutcome::Done(makepad_ai_services::wire::ToolResult::unavailable(&call.call_id, "test")) + } + } + + static AI_PROBE: Probe = Probe("assistant-probe", &["storage", "octos.session.open", "octos.turn.start"]); + static PLAIN_PROBE: Probe = Probe("plain-probe", &["storage", "net"]); + static UNGRANTED_PROBE: Probe = Probe("ungranted-probe", &["octos.turn.start"]); + + #[test] + fn a_granted_module_gets_its_service_at_creation_and_others_get_none() { + policy().allow("assistant-probe", ["octos.session.open", "octos.turn.start"]); + let mut cx = Cx::new(Box::new(|_, _| {})); + cx.with_vm(makepad_widgets::script_mod); + let mut host = crate::module_host::ModuleHost::default(); + host.create(&mut cx, 1, &AI_PROBE, AI_PROBE.open_schema().empty_open().unwrap(), dvec2(400.0, 700.0)).unwrap(); + assert!(CLAIMED.load(Ordering::SeqCst), "the granted module got a scoped service"); + assert!(host.assistant_of(1).is_some()); + host.create(&mut cx, 2, &PLAIN_PROBE, PLAIN_PROBE.open_schema().empty_open().unwrap(), dvec2(400.0, 700.0)).unwrap(); + assert!(!CLAIMED.load(Ordering::SeqCst), "a module without assistant services gets none"); + assert!(host.assistant_of(2).is_none(), "no peer is allocated for it"); + host.create(&mut cx, 3, &UNGRANTED_PROBE, UNGRANTED_PROBE.open_schema().empty_open().unwrap(), dvec2(400.0, 700.0)).unwrap(); + assert!(!CLAIMED.load(Ordering::SeqCst), "declaring is not being granted"); + assert!(host.assistant_of(3).is_none()); + // An offer never outlives its create: nothing is left to claim. + assert!(octosense_app_peers::injection::claim("assistant-probe", "i1g1").is_none()); + assert!(host.teardown(&mut cx, 1)); + } + + /// The real Rinx module: hosted from creation, with the shell's service, + /// and no kernel started by creating it (ADR 0007 criterion 7). + #[cfg(feature = "app-rinx")] + #[test] + fn rinx_is_hosted_with_the_shells_service_and_starts_no_kernel() { + let _one_rinx = super::RINX_INSTANCE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let mut cx = Cx::new(Box::new(|_, _| {})); + cx.with_vm(makepad_widgets::script_mod); + let mut host = crate::module_host::ModuleHost::default(); + let module = &rinx::module::RINX_MODULE; + assert!(module.capabilities().contains(&"octos.turn.start"), "Rinx declares its assistant needs"); + host.create(&mut cx, 7, module, module.open_schema().empty_open().unwrap(), dvec2(400.0, 700.0)).unwrap(); + assert!(host.assistant_of(7).is_some(), "the shell gave Rinx a scoped service"); + assert!(rinx::octos_service::is_hosted(), "hosted mode comes from module creation"); + let service = rinx::octos_service::service().expect("Rinx took the injected service"); + assert_eq!(service.deployment(), octosense_app_peers::Deployment::Hosted); + assert_eq!(service.settings_entry(), octosense_app_peers::SettingsEntry::Host); + assert!(!octosense_octos_core::status().running, "creating Rinx starts no kernel"); + assert!(host.teardown(&mut cx, 7)); + } +} diff --git a/src/apps.rs b/src/apps.rs index 5b87b387..3670ad7f 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -10,6 +10,12 @@ //! `~/.makepad/wm/apps.splash` says otherwise (a settings file, never an //! environment variable) or a dev run passes `--module `. The uber //! builds ignore the switch: everything is a module there. +//! +//! App Hub (feature `app-hub`, on by default) adds the apps its Card runner +//! hosts, the same way OctoSense ROM's Home does: the system apps this build +//! ships as contained script bundles (`os.news`, … ADR 0004) and the apps the +//! person installed from the App Hub catalog (`hub:`). Neither +//! has a process form; the linked `card` module runs every one of them. use makepad_app_module::AppModule; use std::collections::HashMap; @@ -34,7 +40,7 @@ impl Default for AppRegistry { /// A linked module by id, without a registry: what the launcher asks. pub fn is_linked(id: &str) -> bool { - linked_modules().iter().any(|m| m.id() == id) + linked_modules().iter().any(|m| m.id() == id) || is_card_app(id) } /// Mobile includes its bundled modules automatically; desktop opts in with @@ -46,17 +52,210 @@ fn linked_modules() -> Vec<&'static dyn AppModule> { out.push(&octosense_reference::REFERENCE_MODULE); #[cfg(any(feature = "app-sheets", target_os = "android", target_os = "ios"))] out.push(&makepad_sheets::SHEETS_MODULE); - #[cfg(any(feature = "app-photos", target_os = "android", target_os = "ios"))] + // Makepad's native Photos, for comparison only: by default Photos is the + // system app (`os.photos`), and a linked module of the same id wins. + #[cfg(feature = "app-photos")] out.push(&makepad_photos::PHOTOS_MODULE); - #[cfg(any(feature = "app-appcard", target_os = "android", target_os = "ios"))] + // AppCard is opt-in on every target (not shipped by default for now). + #[cfg(feature = "app-appcard")] out.push(&octosense_appcard::APPCARD_MODULE); + #[cfg(feature = "app-rinx")] + out.push(&rinx::module::RINX_MODULE); + // The trust anchor stays native: the store, and the runner every system + // and installed app is hosted by. + #[cfg(feature = "app-hub")] + { + out.push(&octosense_app_hub_app::APP_HUB_MODULE); + out.push(&octosense_app_hub_app::CARD_MODULE); + } out } -/// An installed host has no checkout catalog. Its linked modules carry all -/// the information needed to populate the launcher without filesystem paths. +/// Manifest ids live in a separate namespace from built-ins and catalog rows +/// (catalog ids cannot contain a colon). +pub fn installed_launch_id(manifest_id: &str) -> String { + format!("hub:{manifest_id}") +} + +/// The manifest id the `card` module opens for a launcher row: `os.` +/// for a system app, the installed app's own id for `hub:`. +pub fn card_manifest_id(app: &crate::clients::AppDef) -> Option<&str> { + if app.bin != "card" { + return None; + } + app.id.strip_prefix("hub:").or_else(|| app.args.iter().find_map(|a| a.strip_prefix(SYSTEM_ARG))) +} + +/// A system app's launcher row carries its manifest id as this argument. +const SYSTEM_ARG: &str = "--system="; + +fn card_row(id: String, label: String, args: Vec) -> crate::clients::AppDef { + crate::clients::AppDef { + id, + label, + bin: "card".into(), + package: String::new(), + dir: String::new(), + manifest: None, + args, + policy: crate::clients::LaunchPolicy::OrFocus, + target_dir: None, + } +} + +/// System apps (ADR 0004): first-party apps from OctoSense-System-Apps that +/// App Hub ships as contained script bundles, run by the Card runner. Each +/// keeps its short launcher id (`mail` for `os.mail`), so its icon and dock +/// place are the ones that id always had. They take precedence over catalog +/// rows of the same id (`clients::registry`); a linked native module of the +/// same id wins, for comparison builds (`app-photos`). +pub fn system_card_apps() -> Vec { + #[cfg(feature = "app-hub")] + { + register_host_services(); + let native: Vec<&str> = linked_modules().iter().map(|m| m.id()).collect(); + return octosense_app_hub_app::system_apps() + .into_iter() + .filter_map(|app| { + let short = app.id.strip_prefix("os.")?; + (!native.contains(&short)) + .then(|| card_row(short.into(), app.name.into(), vec![format!("{SYSTEM_ARG}{}", app.id)])) + }) + .collect(); + } + #[allow(unreachable_code)] + Vec::new() +} + +/// The services contained apps call through `host.request` (ADR 0004), +/// registered once, before the first system app can open: +/// +/// - `mail` keeps accounts and passwords for the Mail app. `mail_demo` in +/// MAKEPAD_APP_CONFIG serves a demo mailbox from a file vault instead (no +/// keychain, no network): `MAKEPAD_APP_CONFIG='{"mail_demo":true}'`. +/// - `llm` keeps the octos kernel's LLM providers for AI providers +/// (`os.ai-providers`), written to the kernel's profile under the shell's +/// octos core dir (`octosense_octos_core::core_dir()`: `OCTOS_APP_CORE_DIR`, +/// else `~/octos-home/.octos`), keys in the keychain entry octos reads +/// (`OCTOSENSE_LLM_VAULT=file` keeps them in the owner-only profile). No +/// camera scanner: a phone's profile QR is imported from a picture of it +/// (the open panel, or an image dropped on the import sheet: `llm_image`), +/// or by pasting its text. With `octos-core` (default) the service restarts +/// the kernel after a change; its consumers (AppCard) reconnect. +#[cfg(feature = "app-hub")] +pub fn register_host_services() { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + let demo = std::env::var("MAKEPAD_APP_CONFIG") + .ok() + .and_then(|text| makepad_strict_json::parse(text.as_bytes()).ok()) + .and_then(|config| config.get("mail_demo").and_then(|v| v.as_bool())) + .unwrap_or(false); + if demo { + octosense_mail_service::register_demo() + } else { + octosense_mail_service::register() + } + let mut llm = octosense_llm_service::Options::default(); + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + let core_dir = octosense_octos_core::core_dir(); + #[cfg(not(any(feature = "octos-core", target_os = "android", target_os = "ios")))] + let core_dir = octosense_llm_config::profile::default_core_dir(); + if let Some(dir) = core_dir { + llm = llm.core_dir(dir); + } + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + llm = llm.image_picker(crate::llm_image::picker()).image_drops(true); + } + octosense_llm_service::register_with(llm); + }); +} + +/// Configure the shell's octos kernel (`octosense_octos_core`) once, at +/// startup, before anything connects to it. Nothing runs until a consumer +/// (AppCard, Rinx) connects; then one kernel per process: on a desktop the +/// binary `OCTOS_APP_CORE_BIN` names (none otherwise), on Android the APK's +/// `liboctos.so`. Its diagnostics go to the shell's log. +#[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] +pub fn configure_octos_kernel(data_dir: Option) { + let mut options = octosense_octos_core::Options::default().log(|line| makepad_widgets::log!("{line}")); + if let Some(dir) = data_dir.filter(|d| !d.is_empty()) { + options = options.app_data_dir(dir); + } + octosense_octos_core::configure(options); + match octosense_octos_core::launch() { + Ok(_) => makepad_widgets::log!("octos: kernel service ready (starts on first use), core dir {:?}", octosense_octos_core::core_dir()), + Err(why) => makepad_widgets::log!("octos: {why}; the providers are still saved under {:?}", octosense_octos_core::core_dir()), + } +} + +/// Apps App Hub installed: each is an app of its own in the launcher, hosted +/// by the linked `card` module under its `hub:` identity. Read +/// fresh each time, so an install shows up without a restart. +pub fn installed_card_apps() -> Vec { + #[cfg(feature = "app-hub")] + if let Some(root) = octosense_app_hub_app::data_root_if_set() { + return octosense_app_hub_app::installed_apps(&root) + .into_iter() + .map(|app| card_row(installed_launch_id(&app.id), app.name, Vec::new())) + .collect(); + } + Vec::new() +} + +/// Every launcher row the Card runner opens: system apps, then installed ones. +pub fn card_apps() -> Vec { + let mut apps = system_card_apps(); + apps.extend(installed_card_apps()); + apps +} + +/// Whether the `card` module hosts `id`. Installed ids carry their prefix, +/// so only they read the installed library. +fn is_card_app(id: &str) -> bool { + if id.starts_with("hub:") { + installed_card_apps().iter().any(|app| app.id == id) + } else { + system_card_apps().iter().any(|app| app.id == id) + } +} + +/// Launch-or-focus: a Card app focuses only an instance of that same app, +/// never a built-in (or another installed app) whose name it shares. +pub fn matches_running_app(app: &crate::clients::AppDef, running_id: &str, title: &str) -> bool { + if app.bin == "card" || running_id.starts_with("hub:") { + running_id == app.id + } else { + crate::clients::word_match(running_id, &app.id) || crate::clients::word_match(title, &app.id) + } +} + +/// What a launch of `app` opens `module` with. The Card runner opens the app +/// the launcher row names — a system app by its `os.*` manifest id, an +/// installed one by its own; every other module opens empty. +pub fn module_open(module: &'static dyn AppModule, app: &crate::clients::AppDef) -> Result { + let schema = module.open_schema(); + if module.id() != "card" { + return schema.empty_open(); + } + let manifest_id = card_manifest_id(app).ok_or_else(|| format!("{} names no app for the card runner", app.id))?; + schema.validate(&format!("{{\"app\":{}}}", makepad_strict_json::Value::Str(manifest_id.into()).to_json()), &[]) +} + +/// An installed host has no checkout catalog. Its linked modules, the system +/// apps and the installed apps carry all the information needed to populate +/// the launcher without filesystem paths. pub fn bundled_catalog() -> Vec { - linked_modules().iter().map(|module| crate::clients::AppDef { + let mut catalog = bundled_modules_catalog(); + catalog.extend(card_apps()); + catalog +} + +/// The linked modules as launcher rows. The `card` host module is not an app +/// a person opens; the apps it runs are. +pub fn bundled_modules_catalog() -> Vec { + linked_modules().iter().filter(|module| module.id() != "card").map(|module| crate::clients::AppDef { id: module.id().into(), label: module.label().into(), bin: module.id().into(), @@ -104,9 +303,16 @@ impl AppRegistry { registry } - /// The linked module for an app, if this build has one. + /// The linked module for an app, if this build has one. A system or + /// installed app has none of its own: the `card` module hosts it. pub fn module(&self, id: &str) -> Option<&'static dyn AppModule> { - self.modules.iter().copied().find(|m| m.id() == id) + if let Some(module) = self.modules.iter().copied().find(|m| m.id() == id) { + return Some(module); + } + if is_card_app(id) { + return self.modules.iter().copied().find(|m| m.id() == "card"); + } + None } /// How a launch of `id` is hosted. On a desktop: Module only when a @@ -117,6 +323,14 @@ impl AppRegistry { if !crate::host::processes_available() { return if self.module(id).is_some() { Hosting::Module } else { Hosting::Process }; } + // The store has no process form, and neither has a system or + // installed app: the `card` module hosts them on every platform. + if id == "apphub" && self.module(id).is_some() { + return Hosting::Module; + } + if self.modules.iter().any(|m| m.id() == "card") && !self.modules.iter().any(|m| m.id() == id) && is_card_app(id) { + return Hosting::Module; + } match self.overrides.get(id) { Some(Hosting::Module) if self.module(id).is_some() => Hosting::Module, _ => Hosting::Process, @@ -169,12 +383,33 @@ mod tests { #[test] fn bundled_apps_open_without_catalog_files_or_child_processes() { use makepad_widgets::*; + let _one_rinx = crate::app_peers_host::RINX_INSTANCE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let catalog = bundled_catalog(); - assert_eq!(catalog.iter().map(|app| app.id.as_str()).collect::>(), - ["reference", "sheets", "photos", "appcard"]); + // AppCard is opt-in (`app-appcard`), not part of `mobile-apps`. + let expected: &[&str] = if cfg!(feature = "app-appcard") { + &["reference", "sheets", "appcard", "apphub", "news", "photos", "maps", "camera", "mail", "ai-providers"] + } else { + &["reference", "sheets", "apphub", "news", "photos", "maps", "camera", "mail", "ai-providers"] + }; + // Rinx (app-rinx, in `default`) is linked before App Hub. + let mut expected: Vec<&str> = expected.to_vec(); + if cfg!(feature = "app-rinx") { + let at = expected.iter().position(|id| *id == "apphub").unwrap(); + expected.insert(at, "rinx"); + } + assert_eq!(catalog.iter().map(|app| app.id.as_str()).collect::>(), expected); assert!(catalog.iter().all(|app| app.manifest.is_none())); - assert_eq!(catalog[0].policy, crate::clients::LaunchPolicy::AlwaysNew); + // The system apps have no native module: the Card runner hosts them, + // launched by their manifest id (ADR 0004). let registry = AppRegistry::default(); + for id in ["news", "photos", "maps", "camera", "mail", "ai-providers"] { + let app = catalog.iter().find(|app| app.id == id).unwrap(); + assert_eq!(card_manifest_id(app), Some(format!("os.{id}").as_str())); + assert_eq!(registry.module(id).map(|m| m.id()), Some("card")); + assert_eq!(registry.hosting(id), Hosting::Module, "{id} has no process form"); + } + let catalog: Vec<_> = catalog.into_iter().filter(|app| app.bin != "card").collect(); + assert_eq!(catalog[0].policy, crate::clients::LaunchPolicy::AlwaysNew); let mut cx = Cx::new(Box::new(|_, _| {})); cx.with_vm(makepad_widgets::script_mod); let mut host = crate::module_host::ModuleHost::default(); @@ -195,6 +430,40 @@ mod tests { } } + #[test] + fn installed_card_identity_never_focuses_a_builtin_with_the_same_name() { + let mut app = card_row("news".into(), "News".into(), Vec::new()); + app.bin = "news".into(); + assert!(matches_running_app(&app, "news", "News")); + assert!(!matches_running_app(&app, "hub:news", "News")); + app.id = installed_launch_id("news"); + app.bin = "card".into(); + assert_eq!(card_manifest_id(&app), Some("news")); + assert!(matches_running_app(&app, "hub:news", "News")); + assert!(!matches_running_app(&app, "news", "News")); + assert!(!matches_running_app(&app, "hub:news-other", "News")); + // A system app is opened by the manifest id its row carries. + let system = card_row("mail".into(), "Mail".into(), vec![format!("{SYSTEM_ARG}os.mail")]); + assert_eq!(card_manifest_id(&system), Some("os.mail")); + assert!(!matches_running_app(&system, "mailer", "Mail"), "a Card app focuses only itself"); + } + + /// The system apps this build packs (system-apps.json): each a Card app + /// under its short id, and each bundle registered with the runner. + #[cfg(feature = "app-hub")] + #[test] + fn the_system_apps_ship_as_card_apps() { + let ids: Vec = system_card_apps().into_iter().map(|app| app.id).collect(); + assert_eq!(ids, ["news", "photos", "maps", "camera", "mail", "ai-providers"]); + let registry = AppRegistry::default(); + for id in &ids { + assert_eq!(registry.hosting(id), Hosting::Module); + assert!(is_linked(id)); + } + assert_eq!(registry.hosting("apphub"), Hosting::Module, "the store has no process form"); + assert!(octosense_app_hub_app::system_icon("camera").is_some(), "Camera ships its own icon"); + } + #[test] fn overrides_parse_the_settings_shape_and_ignore_noise() { let text = "// which apps run in-process\n{\n sheets: Module,\n Terminal: process\n files: Sideways\n nonsense\n}\n"; @@ -220,7 +489,7 @@ mod tests { } } -#[cfg(all(test, feature = "mobile-apps"))] +#[cfg(all(test, feature = "app-appcard"))] mod appcard_isolate_tests { /// The app's cards are Splash widgets, each in an ISOLATE that is minted /// without the framework's `sys`/`agent` engine; the AppCard module must diff --git a/src/clients.rs b/src/clients.rs index 6ed8d46b..c56cb4fa 100644 --- a/src/clients.rs +++ b/src/clients.rs @@ -123,12 +123,37 @@ fn manifest_value(manifest: &str, key: &str) -> Option { /// The app registry: the applications this WM is built around, in menu /// order. Curated on purpose — every row is one we run and verify, not a -/// scan of whatever the workspace happens to contain. -pub fn registry() -> &'static [AppDef] { - match crate::octosense::catalog::loaded() { - Ok(apps) => apps, - Err(_) => &[], - } +/// scan of whatever the workspace happens to contain: the catalog's rows, +/// the linked modules, then the apps App Hub's Card runner hosts (system +/// apps and installed apps, read fresh so an install needs no restart). +pub fn registry() -> Vec { + let base = crate::octosense::catalog::loaded().as_ref().cloned().unwrap_or_default(); + merge_catalog( + base, + crate::apps::bundled_modules_catalog(), + crate::apps::system_card_apps(), + crate::apps::installed_card_apps(), + ) +} + +/// One row per id. A system app replaces a catalog row of the same id in +/// place (it keeps that row's menu position): the system Mail, not +/// Makepad's example Mail, even in a personal catalog. Otherwise the first +/// definition wins — catalog rows, then linked modules. Installed ids live +/// under `hub:`, so a manifest named after a built-in becomes its own row +/// beside it, never a replacement. The internal `card` host is no row. +fn merge_catalog(base: Vec, bundled: Vec, system: Vec, installed: Vec) -> Vec { + let mut system: Vec> = system.into_iter().map(Some).collect(); + let mut take_system = |id: &str| system.iter_mut().find(|a| a.as_ref().is_some_and(|a| a.id == id)).and_then(Option::take); + let mut rows: Vec = Vec::new(); + for app in base.into_iter().chain(bundled) { + rows.push(take_system(&app.id).unwrap_or(app)); + } + rows.extend(system.into_iter().flatten()); + rows.extend(installed); + let mut ids = std::collections::HashSet::new(); + rows.retain(|app| app.id != "card" && ids.insert(app.id.clone())); + rows } /// Registered ids take precedence over binary aliases. A linked module @@ -136,9 +161,9 @@ pub fn registry() -> &'static [AppDef] { /// process form) is still an app: its bundled definition answers, and the /// hosting rules decide whether it may open (`--module ` on a desktop). pub fn find_app(id: &str) -> Option { - registry().iter().find(|a| a.id == id) - .or_else(|| registry().iter().find(|a| a.bin == id)).cloned() - .or_else(|| crate::apps::bundled_catalog().into_iter().find(|a| a.id == id)) + let apps = registry(); + apps.iter().find(|a| a.id == id) + .or_else(|| apps.iter().find(|a| a.bin == id && a.bin != "card")).cloned() } /// `bin/omarchy-launch-or-focus`'s window test, verbatim: @@ -992,6 +1017,22 @@ pub fn spawn_client( mod tests { use super::*; + #[test] + fn system_apps_replace_catalog_rows_and_installed_apps_never_shadow() { + let app = |id: &str, label: &str, bin: &str| AppDef::app(id, label, "", "", bin, LaunchPolicy::OrFocus); + let base = vec![app("browser", "Browser", "browser"), app("mail", "Makepad Mail", "mail"), app("notes", "Notes", "notes")]; + let bundled = vec![app("apphub", "App Hub", "apphub"), app("card", "Internal host", "card")]; + let system = vec![app("news", "News", "card"), app("mail", "Mail", "card")]; + let installed = vec![app("hub:demo", "Demo", "card"), app("hub:mail", "Untrusted Mail", "card")]; + let merged = merge_catalog(base, bundled, system, installed); + assert_eq!( + merged.iter().map(|a| a.id.as_str()).collect::>(), + ["browser", "mail", "notes", "apphub", "news", "hub:demo", "hub:mail"] + ); + // The system Mail took the example's place, not its name only. + assert_eq!((merged[1].label.as_str(), merged[1].bin.as_str()), ("Mail", "card")); + } + /// Cargo's checkout is Cargo's to manage: a build there is invisible to /// `cargo clean`, survives no refetch, and quietly grows the shared /// cache. Apps from the pinned revision build into OctoSense's own tree. @@ -1131,6 +1172,11 @@ mod tests { }; let mut metadata = std::collections::HashMap::new(); for app in registry() { + // Linked modules and the apps the Card runner hosts are no + // process: nothing to build. + if app.package.is_empty() { + continue; + } let manifest = app .manifest .clone() diff --git a/src/desk.rs b/src/desk.rs index 9a6776b9..43c55905 100644 --- a/src/desk.rs +++ b/src/desk.rs @@ -1861,7 +1861,13 @@ impl Widget for WmDesk { // when the workspace returns. self.anims .retain(|client, anim| live.contains(client) || anim.close_t.is_some()); - self.desktop_frames.retain(|client, _| self.anims.contains_key(client)); + let gone: Vec = self.desktop_frames.keys() + .filter(|client| !self.anims.contains_key(client)).copied().collect(); + for client in gone { + if let Some(frame) = self.desktop_frames.remove(&client) { + frame.forget(cx); + } + } // Closing tiles paint under the live ones. let closing: Vec = self diff --git a/src/desktop.rs b/src/desktop.rs index 98197585..ab5e8a49 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -892,7 +892,7 @@ impl DesktopShelf { pub fn app_icon(id: &str) -> Ico { match id { "browser" => Ico::Globe, - "photos" | "image" => Ico::Photo, + "photos" | "image" | "camera" => Ico::Photo, "terminal" => Ico::Keyboard, "files" => Ico::Menu, "sheets" => Ico::Calendar, @@ -902,7 +902,7 @@ pub fn app_icon(id: &str) -> Ico { "mixer" => Ico::Speaker, "vj" => Ico::Headphone, "score" => Ico::Bell, - "route" => Ico::Globe, + "route" | "maps" => Ico::Globe, "fabric" => Ico::Shirt, "fab" => Ico::Refresh, "studio" => Ico::Moon, diff --git a/src/desktop_app.rs b/src/desktop_app.rs index 6ed4ccf0..b392c3bc 100644 --- a/src/desktop_app.rs +++ b/src/desktop_app.rs @@ -97,6 +97,16 @@ impl App { state.roles = roles; state.dragging.clear(); state.style.select(style); + // Extra module windows are a desktop thing: the phone shell's apps are + // full-screen, so their windows close (the apps hear it, as from a person). + self.module_host.set_extra_windows(!style.mobile()); + if style.mobile() { + let windows: Vec<_> = self.module_windows.keys().copied().collect(); + for window in windows { + self.remove_client(cx, window); + } + } + let state = self.state_mut(); if changes_size { state.style.step(1.0); } state.layout.desktop.enabled = style.floating(); for c in state.layout.all_clients() { diff --git a/src/dock_warp.rs b/src/dock_warp.rs index e6a8ada5..0bd0dafe 100644 --- a/src/dock_warp.rs +++ b/src/dock_warp.rs @@ -1,5 +1,7 @@ //! A whole-window texture pulled into its dock icon. No app layout or resize //! occurs during the warp; the same frozen surface is used for restoration. +use makepad_widgets::gauss_view::CaptureGauss; +use makepad_widgets::makepad_draw::overlay::{Overlay, OverlayScope}; use makepad_widgets::*; script_mod! { @@ -53,12 +55,24 @@ pub struct DrawDockWarp { y_flip: f32, } +/// An app's frame in a texture of its own. Everything the app draws — its +/// root, the lists it lifts with `begin_overlay_*` (popups, a blur layer), +/// and the gauss pyramid those may ask for — records into THIS pass tree: +/// the overlay root is the capture's for the duration of `begin`/`end` +/// (never the window's overlay slot, which paints after every window), and +/// `request_window_gauss` inside resolves to a pyramid built from this very +/// texture (`CaptureGauss`). A presenter composites the texture and nothing +/// else, so a window's popups and glass stay in the window's stacking order. pub struct WindowFrame { pass: DrawPass, list: DrawList2d, texture: Texture, _depth: Texture, frozen: bool, + overlay: Overlay, + overlay_scope: Option, + gauss: CaptureGauss, + rect: Rect, } impl WindowFrame { pub fn new(cx: &mut Cx) -> Self { @@ -92,6 +106,10 @@ impl WindowFrame { texture, _depth: depth, frozen: false, + overlay: Overlay { draw_list: DrawList::new(cx) }, + overlay_scope: None, + gauss: CaptureGauss::new(cx), + rect: Rect::default(), } } pub fn frozen(&self) -> bool { @@ -101,6 +119,11 @@ impl WindowFrame { self.pass.draw_pass_id() } pub fn texture(&self) -> &Texture { &self.texture } + /// The capture is going away: its gauss request state is keyed by the + /// pass slot, which the next capture may reuse. + pub fn forget(self, cx: &mut Cx) { + CaptureGauss::forget(cx, self.pass.draw_pass_id()); + } pub fn begin(&mut self, cx: &mut Cx2d, rect: Rect) { self.frozen = false; if std::env::var_os("MAKEPAD_WM_TRACE_WARP").is_some() { @@ -114,11 +137,27 @@ impl WindowFrame { cx.begin_pass(&self.pass, Some(dpi)); self.list.begin_always(cx); cx.begin_root_turtle(root_size, Layout::flow_overlay()); + // From here every overlay the app lifts and every gauss it asks for + // is the capture's own. + self.rect = rect; + let pass = self.pass.draw_pass_id(); + self.overlay_scope = Some(self.overlay.begin_nested_for_pass(cx, pass)); + self.gauss.begin(cx, pass, rect.pos, rect.size, root_size); } pub fn end(&mut self, cx: &mut Cx2d) { + let pass = self.pass.draw_pass_id(); + // The scene (when a pyramid was built) goes under the overlays, + // the overlays last — the window's own order. + let gauss_changed = self.gauss.end(cx, pass, self.rect); + if let Some(scope) = self.overlay_scope.take() { + self.overlay.end_nested(cx, scope); + } cx.end_pass_sized_turtle(); self.list.end(cx); cx.end_pass(&self.pass); + if gauss_changed { + cx.repaint_pass_and_child_passes(pass); + } if std::env::var_os("MAKEPAD_WM_TRACE_WARP").is_some() { log!("warp: capture end"); } diff --git a/src/llm_image.rs b/src/llm_image.rs new file mode 100644 index 00000000..70da8e21 --- /dev/null +++ b/src/llm_image.rs @@ -0,0 +1,155 @@ +//! The desktop's image source for AI providers' QR import (the `llm` host +//! service's `QrImagePicker`, OctoSense-System-Apps +//! `apps/ai-providers/host-service`): Makepad's native open panel filtered to +//! PNG and JPEG, and image files dropped on the AI providers window while +//! its import sheet is up. Either way the service gets the file's bytes and +//! reads the code out of them on a worker; the sheet then asks for the PIN. +//! +//! Test hook: `OCTOSENSE_LLM_TEST_IMAGE=` makes "Choose image" answer +//! with that file at once, without a panel (the remote e2e run is hidden and +//! cannot click through a modal). Development only. +use makepad_widgets::makepad_platform::file_dialogs::{FileDialog, FileDialogAction}; +use makepad_widgets::makepad_platform::thread::SignalToUI; +use makepad_widgets::*; +use octosense_llm_service::{ImageDone, PickError, QrImagePicker}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, OnceLock}; + +/// The system app whose window takes dropped images (its short id). +const APP: &str = "ai-providers"; +/// Larger files are not read at all; the service refuses over 20 MB anyway. +const MAX_FILE_BYTES: u64 = 20 * 1024 * 1024; +const TEST_IMAGE_ENV: &str = "OCTOSENSE_LLM_TEST_IMAGE"; + +fn dialog_id() -> LiveId { + live_id!(octosense_llm_qr_image) +} + +#[derive(Default)] +pub struct DesktopPicker { + /// The pick waiting on the open panel. + pending: Mutex>, + /// The panel should open on the next UI event. + open: AtomicBool, +} + +impl QrImagePicker for DesktopPicker { + fn pick(&self, done: ImageDone) { + if let Some(path) = std::env::var_os(TEST_IMAGE_ENV).filter(|p| !p.is_empty()) { + let path = PathBuf::from(path); + std::thread::spawn(move || done(read_image(&path))); + return; + } + if let Some(earlier) = self.pending.lock().unwrap().replace(done) { + earlier(Err(PickError::Cancelled)); + } + self.open.store(true, Ordering::SeqCst); + SignalToUI::set_ui_signal(); + } +} + +/// The one picker the service is registered with. +pub fn picker() -> Arc { + static PICKER: OnceLock> = OnceLock::new(); + PICKER.get_or_init(Arc::default).clone() +} + +fn is_image(path: &Path) -> bool { + matches!( + path.extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase).as_deref(), + Some("png" | "jpg" | "jpeg") + ) +} + +/// A chosen or dropped file's bytes, off the UI thread. +fn read_image(path: &Path) -> Result, PickError> { + let size = std::fs::metadata(path).map_err(|e| PickError::Failed(e.kind().to_string()))?.len(); + if size > MAX_FILE_BYTES { + return Err(PickError::Failed("larger than 20 MB".into())); + } + std::fs::read(path).map_err(|e| PickError::Failed(e.kind().to_string())) +} + +/// On every UI event: open the panel a pick asked for. +pub fn open_requested(cx: &mut Cx) { + let picker = picker(); + if picker.open.swap(false, Ordering::SeqCst) { + cx.open_select_file_dialog( + FileDialog::new() + .set_id(dialog_id()) + .set_title("Choose a picture of the provider code".into()) + .add_filter("Images".into(), vec!["png".into(), "jpg".into(), "jpeg".into()]), + ); + } +} + +/// The panel's answer, from the actions pass. +pub fn handle_actions(actions: &Actions) { + for action in actions { + let Some(answer) = action.downcast_ref::() else { + continue; + }; + let result = match answer { + FileDialogAction::FileSelected { id, paths } if *id == dialog_id() => paths.first().cloned().ok_or(PickError::Cancelled), + FileDialogAction::FileCancelled { id } if *id == dialog_id() => Err(PickError::Cancelled), + _ => continue, + }; + let Some(done) = picker().pending.lock().unwrap().take() else { + continue; + }; + match result { + Ok(path) if is_image(&path) => { + std::thread::spawn(move || done(read_image(&path))); + } + Ok(_) => done(Err(PickError::Failed("not a PNG or JPEG file".into()))), + Err(e) => done(Err(e)), + } + } +} + +/// The one image file a drag or drop carries. +fn dropped_image(items: &[DragItem]) -> Option { + match items { + [DragItem::FilePath { path, .. }] if is_image(Path::new(path)) => Some(PathBuf::from(path)), + _ => None, + } +} + +/// A drag or drop of one image file over the AI providers window while its +/// import sheet waits for one: answered "copy", and on the drop the file goes +/// to the service. `app_at` names the app whose window is at a point. `true` +/// when the event was the service's. +pub fn handle_drop(event: &Event, app_at: impl Fn(Vec2d) -> Option) -> bool { + let over_import = |abs: Vec2d, items: &[DragItem]| { + let path = dropped_image(items)?; + (octosense_llm_service::wants_image() && app_at(abs).as_deref() == Some(APP)).then_some(path) + }; + match event { + Event::Drag(e) => { + if over_import(e.abs, &e.items).is_some() { + *e.response.lock().unwrap() = DragResponse::Copy; + *e.handled.lock().unwrap() = true; + return true; + } + false + } + Event::Drop(e) => { + let Some(path) = over_import(e.abs, &e.items) else { + return false; + }; + *e.handled.lock().unwrap() = true; + std::thread::spawn(move || { + // A file that cannot be read is offered as nothing: the sheet + // says it is not an image. + let bytes = read_image(&path).unwrap_or_default(); + if !octosense_llm_service::offer_image(bytes) { + log!("llm: the import sheet closed before the dropped image was read"); + } + }); + log!("llm: image dropped on the import sheet"); + true + } + _ => false, + } +} diff --git a/src/main.rs b/src/main.rs index 66cfdf7f..4fb3a135 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use makepad_widgets::makepad_platform::thread::{Lane, SignalToUI, TaskHandle}; use makepad_widgets::*; mod ai_bus; +mod app_peers_host; mod apps; mod binds; mod clients; @@ -30,6 +31,8 @@ mod dock_warp; mod host; mod hub; mod layout; +#[cfg(all(feature = "app-hub", not(any(target_os = "android", target_os = "ios"))))] +mod llm_image; mod octosense; mod module_host; mod module_view; @@ -61,7 +64,7 @@ use shell::panels::ShellPanelAction; use ai_bus::{AiBus, Route}; use apps::{AppRegistry, Hosting}; use makepad_ai_services::wire::{ServiceCall, ServiceDown, ToolResult}; -use makepad_app_module::{AppModule, ExecOutcome, ModuleUpstream}; +use makepad_app_module::{AppModule, ExecOutcome, ModuleUpstream, WindowRequest}; use module_host::ModuleHost; use pane_links::{PaneCall, PaneLinks}; use makepad_widgets::ai_slot::AiSlotRequests; @@ -386,6 +389,10 @@ pub struct App { /// isolate each, seated in a `ModuleTile`. #[rust] module_host: ModuleHost, + /// Extra windows module instances opened (`ModuleWindows`): window + /// client -> (owner instance's client, the instance's key for it). + #[rust] + module_windows: HashMap, /// The in-process transport to an assistant seated in the pane as a /// module: the WM's services as links its registry adopts (pane_links.rs). #[rust] @@ -552,14 +559,24 @@ impl App { return; }; let app = &app; + // An installed app opens only while the App Hub catalog still admits + // it. A system app (`os.*`) ships with the build and answers to no + // catalog. + #[cfg(feature = "app-hub")] + if let Some(manifest_id) = apps::card_manifest_id(app).filter(|id| !id.starts_with("os.")) { + if let Err(error) = octosense_app_hub_app::catalog::try_may_open_from_environment( + octosense_app_hub_app::data_root(cx), manifest_id) { + self.notify(cx, "Could not open app", &error); + return; + } + } let hub_port = self.state_mut().hub_port; // launch-or-focus for non-terminal apps: `omarchy-launch-or-focus` // matches `\b\b` case-insensitively against the window's - // CLASS OR TITLE and focuses the first hit. + // CLASS OR TITLE and focuses the first hit. A Card app focuses only + // an instance of itself (`apps::matches_running_app`). if app.policy == LaunchPolicy::OrFocus { - let pattern = app.id.clone(); - let pattern = pattern.as_str(); let mut existing: Vec = self .state_mut() .clients @@ -572,8 +589,7 @@ impl App { && !slot.pane && !slot.is_preview && slot.closing.is_none() - && (clients::word_match(&slot.app, pattern) - || clients::word_match(&slot.title, pattern)) + && apps::matches_running_app(app, &slot.app, &slot.title) }) .map(|(id, _)| *id) .collect(); @@ -591,7 +607,7 @@ impl App { // isolate of its own — never a process, never the pool. if self.apps.hosting(app_id) == Hosting::Module { if let Some(module) = self.apps.module(app_id) { - self.launch_module(cx, module); + self.launch_module(cx, module, app); return; } } @@ -1367,10 +1383,27 @@ impl App { self.request_close(cx, focus); } + /// App Hub finished installing (or updating) `id`. An update replaces + /// the bundle on disk, but a running Card keeps its old code and + /// permissions: end its instances so the next open takes the new one. + #[cfg(feature = "app-hub")] + fn installed_app_changed(&mut self, cx: &mut Cx, id: &str) { + let launch_id = apps::installed_launch_id(id); + let clients: Vec<_> = self.state_mut().clients.iter() + .filter_map(|(&client, slot)| (slot.app == launch_id).then_some(client)) + .collect(); + for client in clients { + self.request_close(cx, client); + } + octosense_app_hub_app::icons::invalidate(); + self.redraw_all(cx); + } + fn request_close(&mut self, cx: &mut Cx, client: ClientId) { // A module instance has no process to ask politely and nothing to // reap later: it ends now, through the same removal as a death. - if self.module_host.is_module(client) { + // So does one of its extra windows. + if self.module_host.is_module(client) || self.module_windows.contains_key(&client) { self.remove_client(cx, client); self.update_bar(cx); return; @@ -1425,6 +1458,22 @@ impl App { fn remove_client(&mut self, cx: &mut Cx, client: ClientId) { log!("wm: removing client {}", client); + // An instance's extra window: the tile lets go of the root (the + // instance keeps the widget), and the instance hears it was closed. + if let Some((owner, key)) = self.module_windows.remove(&client) { + self.desk(cx) + .borrow_mut::() + .map(|mut d| d.with_module_view(cx, client, |cx, v| v.clear_root(cx))); + self.module_host.notify_window_closed(owner, key); + } + // An instance going away takes its extra windows with it, first. + if self.module_host.is_module(client) { + let windows: Vec = self.module_windows.iter() + .filter(|(_, (owner, _))| *owner == client).map(|(w, _)| *w).collect(); + for window in windows { + self.remove_client(cx, window); + } + } // A module instance: the tile lets go of the root FIRST, then the // instance and its isolate go (module_host.rs). The bus hears an // Unregister for it below, like for any client. @@ -1920,8 +1969,8 @@ impl App { /// Open `module` as an instance of its own in this process: an isolate, /// a tile in the layout, a local endpoint on the bus. The ordinary /// launch path minus everything a process needs. - fn launch_module(&mut self, cx: &mut Cx, module: &'static dyn AppModule) { - let open = match module.open_schema().empty_open() { + fn launch_module(&mut self, cx: &mut Cx, module: &'static dyn AppModule, app: &clients::AppDef) { + let open = match apps::module_open(module, app) { Ok(open) => open, Err(e) => { log!("wm: {} cannot open without arguments: {}", module.id(), e); @@ -1931,6 +1980,8 @@ impl App { let id = self.next_id; self.next_id += 1; let area = self.desk_area(cx); + // Extra windows are a desktop thing; the phone shell's apps are full-screen. + self.module_host.extra_windows = !self.state_mut().style.target.mobile(); if let Err(e) = self.module_host.create(cx, id, module, open, dvec2(area.w, area.h)) { log!("wm: module {} failed to start: {}", module.id(), e); return; @@ -1941,7 +1992,7 @@ impl App { }; self.state_mut() .clients - .insert(id, clients::ClientSlot::module(id, module.id(), module.label())); + .insert(id, clients::ClientSlot::module(id, &app.id, &app.label)); let gap = self.state_mut().gap; self.state_mut().layout.insert(id, area, gap); // The tile is a module tile from its first draw; the root is seated @@ -1960,7 +2011,7 @@ impl App { let frame = self.ai_bus.register_local(id, manifest); self.send_to_pane(frame); } - log!("wm: launched {} as client {} (in-process)", module.id(), id); + log!("wm: launched {} as client {} (in-process, {})", app.id, id, module.id()); self.activate_client(cx, id); self.update_bar(cx); self.redraw_all(cx); @@ -1989,6 +2040,50 @@ impl App { } } + /// Instances' extra-window requests (`ModuleWindows`): each root becomes + /// a module tile of its own, drawn and fed events in the owner's isolate. + fn drain_module_windows(&mut self, cx: &mut Cx) { + for (owner, vm_id, app, request) in self.module_host.take_window_requests() { + let existing = |this: &Self, key: LiveId| this.module_windows.iter() + .find(|(_, (o, k))| *o == owner && *k == key).map(|(w, _)| *w); + match request { + WindowRequest::Open { key, title, root, size: _ } => { + if let Some(window) = existing(self, key) { + self.activate_client(cx, window); + continue; + } + let id = self.next_id; + self.next_id += 1; + let area = self.desk_area(cx); + self.state_mut().clients.insert(id, clients::ClientSlot::module(id, app, &title)); + let gap = self.state_mut().gap; + self.state_mut().layout.insert(id, area, gap); + self.desk(cx).borrow_mut::().map(|mut d| { + d.mark_module(id); + d.with_module_view(cx, id, |cx, v| v.set_root(cx, id, vm_id, root)); + }); + self.module_windows.insert(id, (owner, key)); + log!("wm: {} (client {}) opened window {:?} as client {}", app, owner, title, id); + self.activate_client(cx, id); + self.update_bar(cx); + self.redraw_all(cx); + } + WindowRequest::Close { key } => { + // The instance closed it itself: no report back, or a + // stale "closed" could end a window it reopens later. + if let Some(window) = existing(self, key) { + self.module_windows.remove(&window); + self.desk(cx) + .borrow_mut::() + .map(|mut d| d.with_module_view(cx, window, |cx, v| v.clear_root(cx))); + self.remove_client(cx, window); + self.update_bar(cx); + } + } + } + } + } + /// Results in-process executors answered later, up to the pane: down /// the instance's link when the assistant is in-process, as a frame to /// the aichat child otherwise. @@ -3858,6 +3953,16 @@ fn scan_theme_color(source: &str, key: &str) -> Option { impl MatchEvent for App { fn handle_startup(&mut self, cx: &mut Cx) { + // The octos kernel is a shell service: configured here, started when + // a consumer (AppCard, Rinx) connects. AI providers' `llm` service + // writes its profile and restarts it after a change. + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + apps::configure_octos_kernel(cx.get_data_dir()); + // Where App Hub keeps what it installs: `$OCTOSENSE_APP_DATA`, else + // `apps/` in the platform data directory or OctoSense's own state. + #[cfg(feature = "app-hub")] + octosense_app_hub_app::set_data_root(cx.get_data_dir().map(std::path::PathBuf::from) + .unwrap_or_else(octosense::paths::home).join("apps")); // CLI: --import-theme pulls an omarchy theme and converts // it to splash before the desktop appears. let mut args = std::env::args(); @@ -4050,6 +4155,8 @@ impl MatchEvent for App { } fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) { + #[cfg(all(feature = "app-hub", not(any(target_os = "android", target_os = "ios"))))] + llm_image::handle_actions(actions); if self.gallery { let gallery = self.ui.widget(cx, ids!(shell_gallery)); { @@ -4063,6 +4170,14 @@ impl MatchEvent for App { let Some(wa) = action.as_widget_action() else { continue; }; + // The store opens what it offers: a built-in by id, an installed + // app by its manifest id. + #[cfg(feature = "app-hub")] + match wa.cast::() { + octosense_app_hub_app::AppHubAction::Launch(id) => self.launch_app(cx, &id), + octosense_app_hub_app::AppHubAction::OpenInstalled(id) => self.launch_app(cx, &apps::installed_launch_id(&id)), + octosense_app_hub_app::AppHubAction::None => {} + } // The shell surfaces: the bar's presses and wheel, the menu's // activations, the flyouts' controls. match wa.cast::() { @@ -4187,9 +4302,14 @@ impl MatchEvent for App { fn handle_signal(&mut self, cx: &mut Cx) { if self.state.is_some() { + #[cfg(feature = "app-hub")] + for id in octosense_app_hub_app::take_completed_installs() { + self.installed_app_changed(cx, &id); + } self.drain_hub(cx); self.drain_client_lines(cx); self.drain_module_upstream(); + self.drain_module_windows(cx); } } } @@ -4293,6 +4413,10 @@ impl AppMain for App { } } self.phone_animation_event(cx,event); + if self.state.as_ref().is_some_and(|state| state.style.target.mobile()) && event.back_pressed() { + self.phone_action(cx, mobile::PhoneHit::Back); + return; + } if let Some(ne) = self.style_frame.is_event(event) { if self.state.is_some() { let dt=if self.style_time==0.0 {0.0}else{(ne.time-self.style_time).min(0.05)}; @@ -4351,6 +4475,24 @@ impl AppMain for App { { return; } + // AI providers' QR import: the open panel a pick asked for, and an + // image dropped on its window while the import sheet waits. + #[cfg(all(feature = "app-hub", not(any(target_os = "android", target_os = "ios"))))] + if self.state.is_some() { + llm_image::open_requested(cx); + if matches!(event, Event::Drag(_) | Event::Drop(_)) { + let desk = self.desk(cx); + let desk = desk.borrow::(); + let state = self.state.as_ref(); + let app_at = |p: Vec2d| { + let client = desk.as_ref()?.window_at(p)?; + state?.clients.get(&client).map(|slot| slot.app.clone()) + }; + if llm_image::handle_drop(event, app_at) { + return; + } + } + } if self.phone_search_event(cx,event) {return;} if self.state.is_some() && self.phone_pointer(cx,event) {return;} if self.state.is_some() && self.snap_event(cx,event) {return;} @@ -4463,6 +4605,9 @@ impl AppMain for App { if let Some(state) = &mut self.state { clients::shutdown_clients(&mut state.clients); } + // Stop the octos kernel, if one runs, and let it release its data dir. + #[cfg(any(feature = "octos-core", target_os = "android", target_os = "ios"))] + octosense_octos_core::shutdown(); } if let Event::Timer(te) = event { self.fire_test_timers(cx, te); diff --git a/src/mobile_app.rs b/src/mobile_app.rs index 8715ff96..8a3f8bc3 100644 --- a/src/mobile_app.rs +++ b/src/mobile_app.rs @@ -178,7 +178,7 @@ impl App { self.state_mut().phone.tiles.note_launch(app_id, host::now()); let Some(app) = crate::clients::find_app(app_id) else { return }; if self.apps.hosting(app_id) == Hosting::Module { - if let Some(module) = self.apps.module(app_id) { self.launch_tile_module(cx, module); } + if let Some(module) = self.apps.module(app_id) { self.launch_tile_module(cx, module, &app); } return; } if !host::processes_available() { return; } @@ -201,20 +201,20 @@ impl App { Err(err) => log!("wm: home tile launch of {} failed: {}", app.id, err), } } - fn launch_tile_module(&mut self, cx: &mut Cx, module: &'static dyn AppModule) { - let open = match module.open_schema().empty_open() { + fn launch_tile_module(&mut self, cx: &mut Cx, module: &'static dyn AppModule, app: &clients::AppDef) { + let open = match crate::apps::module_open(module, app) { Ok(open) => open, Err(e) => { log!("wm: {} cannot open without arguments: {}", module.id(), e); return; } }; let id = self.next_id; self.next_id += 1; - let viewport = self.tile_viewport(module.id()).unwrap_or_else(|| { let a = self.desk_area(cx); dvec2(a.w, a.h) }); + let viewport = self.tile_viewport(&app.id).unwrap_or_else(|| { let a = self.desk_area(cx); dvec2(a.w, a.h) }); if let Err(e) = self.module_host.create(cx, id, module, open, viewport) { log!("wm: module {} failed to start for its home tile: {}", module.id(), e); return; } let Some((manifest, root, vm_id)) = self.module_host.get(id).map(|i| (i.manifest(), i.root.clone(), i.vm_id)) else { return }; - self.state_mut().clients.insert(id, clients::ClientSlot::module(id, module.id(), module.label())); + self.state_mut().clients.insert(id, clients::ClientSlot::module(id, &app.id, &app.label)); self.desk(cx).borrow_mut::().map(|mut d| { d.mark_module(id); d.with_module_view(cx, id, |cx, v| v.set_root(cx, id, vm_id, root)); @@ -227,8 +227,8 @@ impl App { let frame = self.ai_bus.register_local(id, manifest); self.send_to_pane(frame); } - self.state_mut().phone.tiles.bind(module.id(), id, true); - log!("wm: launched {} as client {} for its home tile (in-process)", module.id(), id); + self.state_mut().phone.tiles.bind(&app.id, id, true); + log!("wm: launched {} as client {} for its home tile (in-process)", app.id, id); self.animate_phone(cx); } pub(super) fn configure_phone_mode(&mut self,cx:&mut Cx,previous:desktop::DesktopStyle,style:desktop::DesktopStyle) { @@ -340,7 +340,7 @@ impl App { self.state_mut().phone.keyboard_target=0.0; self.animate_phone(cx); } - fn phone_action(&mut self,cx:&mut Cx,hit:PhoneHit) { + pub(super) fn phone_action(&mut self,cx:&mut Cx,hit:PhoneHit) { match hit { PhoneHit::App(app)=>{ // A running window of the app, a home tile's own client diff --git a/src/module_host.rs b/src/module_host.rs index 0cf19d38..1b6a3d7e 100644 --- a/src/module_host.rs +++ b/src/module_host.rs @@ -38,6 +38,11 @@ pub struct AppInstance { shutdown: Option>, /// Results and publications the executor sent later. upstream: Receiver, + /// The instance's requests for extra windows, and our reports of closes. + pub windows: ModuleWindows, + /// The instance's scoped assistant service (Rinx ADR 0007), when the + /// module declares and is granted `octos.*` services. + assistant: Option, } impl AppInstance { @@ -48,6 +53,9 @@ impl AppInstance { #[derive(Default)] pub struct ModuleHost { + /// Whether new instances may open extra windows: the desktop shell, + /// not the phone shell (whose apps are full-screen). + pub extra_windows: bool, instances: HashMap, next_scope: u64, per_app: HashMap, @@ -58,7 +66,26 @@ pub struct ModuleHost { /// still need its crate resource resolver for their bundled fonts. Expose /// only that existing resolver during theme registration, then remove it. fn apply_module_style(vm: &mut ScriptVm, sheet: &desktop_style::StyleSheet) { - desktop_style::install(vm, sheet.clone()); + let mut inherited = sheet.clone(); + // A nested Splash (a Card app the `card` module runs) replays this + // trusted theme after its ambient `mod.res` has been stripped. Bind only + // the existing bundled-resource resolver in the theme's lexical scope, so + // that replay can still load the style's fonts. This does not publish a + // resource module to the card's source. + inherited.theme = format!( + "mod._octosense_widgets_before_style = mod.widgets\n\ + mod._octosense_prelude_before_style = mod.prelude.widgets\n\ + let crate_resource = mod.prelude.widgets.crate_resource\n{}", inherited.theme); + // widgets_mod rebuilds these namespaces, including the prelude a Card's + // lowered body uses. Retain host additions while letting the freshly + // themed framework names replace their old ones. + inherited.widgets = format!( + "{}\n\ + mod.widgets = {{..mod._octosense_widgets_before_style, ..mod.widgets}}\n\ + mod.prelude.widgets = {{..mod._octosense_prelude_before_style, ..mod.prelude.widgets}}\n\ + mod._octosense_widgets_before_style = nil\n\ + mod._octosense_prelude_before_style = nil\n", inherited.widgets); + desktop_style::install(vm, inherited); vm.with_reload(|vm| { script_eval!(vm, { mod.res = {crate_resource: mod.prelude.widgets.crate_resource} }); makepad_widgets::widgets_mod(vm); @@ -92,8 +119,12 @@ impl ModuleHost { // instance (§3b's mount and the web's IndexedDB sit under it). let storage = cx.storage(&format!("{}.{}", module.id(), instance_no)); let (replies, upstream) = ReplySink::pair(); - let handles = InstanceHandles { scope, storage, viewport: Viewport { size: viewport }, replies }; + let windows = ModuleWindows::new(self.extra_windows); + let handles = InstanceHandles { scope, storage, viewport: Viewport { size: viewport }, replies, windows: windows.clone() }; let vm_id = cx.alloc_splash_vm_with_network(false); + // The assistant is offered to THIS instance for the duration of its + // create only; the module takes it there or never gets it. + let assistant = crate::app_peers_host::offer(module, &scope); let parts = cx.with_script_vm_id_trusted(vm_id, |vm| { // The isolate came up with the stock theme; the WM's palette // retints it exactly as it retints a child process's. @@ -104,6 +135,7 @@ impl ModuleHost { module.register(vm); module.create(vm, open, handles) }); + crate::app_peers_host::withdraw(module, &scope); log!( "wm: module instance {}.{} for client {} in isolate {:?} (scope {})", module.id(), @@ -124,6 +156,8 @@ impl ModuleHost { executor: parts.executor, shutdown: Some(parts.shutdown), upstream, + windows, + assistant, }, ); Ok(()) @@ -145,6 +179,11 @@ impl ModuleHost { } } + /// The assistant service the shell gave this instance, if any. + pub fn assistant_of(&self, client: ClientId) -> Option<&crate::app_peers_host::Assistant> { + self.instances.get(&client)?.assistant.as_ref() + } + pub fn is_module(&self, client: ClientId) -> bool { self.instances.contains_key(&client) } @@ -213,6 +252,32 @@ impl ModuleHost { } /// Every result or publication an executor sent later, with its client. + /// Instances' pending window requests: (owner, its isolate, its app id, request). + pub fn take_window_requests(&mut self) -> Vec<(ClientId, SplashVmId, &'static str, WindowRequest)> { + let mut out = Vec::new(); + for (client, instance) in &self.instances { + for request in instance.windows.take_requests() { + out.push((*client, instance.vm_id, instance.module.id(), request)); + } + } + out + } + + /// The host starts or stops showing extra windows (desktop vs phone shell). + pub fn set_extra_windows(&mut self, on: bool) { + self.extra_windows = on; + for instance in self.instances.values() { + instance.windows.set_supported(on); + } + } + + /// The person closed `owner`'s window `key`. + pub fn notify_window_closed(&self, owner: ClientId, key: LiveId) { + if let Some(instance) = self.instances.get(&owner) { + instance.windows.notify_closed(key); + } + } + pub fn drain_upstream(&mut self) -> Vec<(ClientId, ModuleUpstream)> { let mut out = Vec::new(); for (client, instance) in &self.instances { @@ -232,6 +297,10 @@ impl ModuleHost { if let Some(shutdown) = instance.shutdown.take() { cx.with_script_vm_id_trusted(instance.vm_id, |vm| shutdown(vm)); } + // Release the app's assistant leases; the shared kernel stays. + if let Some(assistant) = instance.assistant.take() { + assistant.release(); + } let vm_id = instance.vm_id; let label = format!("{}.{}", instance.module.id(), instance.instance_no); // The last refs into the isolate's heap go before the heap does. diff --git a/src/module_view.rs b/src/module_view.rs index 34de8c12..280a063f 100644 --- a/src/module_view.rs +++ b/src/module_view.rs @@ -165,15 +165,20 @@ impl Widget for MpModuleView { if matches!(event, Event::KeyDown(_) | Event::KeyUp(_) | Event::TextInput(_)) && !self.focused { return; } + // A press, with the cell that records who claimed it. Tiles see input + // topmost first (desk.rs), so a claimed press belongs to a tile in front. let press = match event { - Event::MouseDown(e) => Some(e.abs), + Event::MouseDown(e) => Some((e.abs, &e.handled)), Event::TouchUpdate(update) => update.touches.iter() .find(|point| point.state == makepad_platform::event::TouchState::Start) - .map(|point| point.abs), + .map(|point| (point.abs, &point.handled)), _ => None, }; - if let Some(abs) = press { - if self.area.is_valid(cx) && self.area.rect(cx).contains(abs) { + let inside = press.is_some_and(|(abs, _)| self.area.is_valid(cx) && self.area.rect(cx).contains(abs)); + if let Some((_, handled)) = press { + // Only a press no window in front took: overlapping windows of one + // app (its extra windows) must not raise the one behind. + if inside && handled.get().is_empty() { if let Some(client) = self.client { // The WM moves focus here (and back to us through // `focus_keyboard`), exactly as for a process tile. @@ -184,6 +189,13 @@ impl Widget for MpModuleView { let entry = enter_isolate(cx, self.vm_id); root.handle_event(cx, event, scope); leave_isolate(cx, entry); + // A press inside this tile is this tile's, even where none of the + // app's widgets took it, so no window behind reacts to it. + if let Some((_, handled)) = press { + if inside && handled.get().is_empty() { + handled.set(self.area); + } + } } fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { @@ -201,7 +213,10 @@ impl Widget for MpModuleView { self.draw_bg.draw_abs(cx, rect); if let Some(root) = self.root.clone() { let entry = enter_isolate(cx, self.vm_id); + // The instance's modals dim and centre within this tile. + let outer = std::mem::replace(&mut cx.global::().0, Some(rect)); root.draw_walk_all(cx, scope, Walk::fill()); + cx.global::().0 = outer; leave_isolate(cx, entry); self.drawn = true; } diff --git a/src/octosense/catalog.rs b/src/octosense/catalog.rs index f1fab059..0509dca8 100644 --- a/src/octosense/catalog.rs +++ b/src/octosense/catalog.rs @@ -159,7 +159,8 @@ pub fn loaded() -> &'static Result, String> { static CATALOG: OnceLock, String>> = OnceLock::new(); CATALOG.get_or_init(|| { let Some(path) = catalog_path()? else { - return Ok(crate::apps::bundled_catalog()); + // `clients::registry` adds the linked modules and Card apps. + return Ok(Vec::new()); }; let path = if path.is_absolute() { path diff --git a/src/octosense/style.rs b/src/octosense/style.rs index 8f196006..b7389260 100644 --- a/src/octosense/style.rs +++ b/src/octosense/style.rs @@ -50,7 +50,9 @@ impl DesktopStyle { pub fn load_sheet(style: DesktopStyle, dark: bool) -> StyleSheet { if style != DesktopStyle::OctoSense { - return StyleSheet::load_with_appearance(style.framework(), dark); + let mut sheet = StyleSheet::load_with_appearance(style.framework(), dark); + sheet.icons = icon_assets(style.framework()); + return sheet; } let read = |name: &str, bundled: &str| { // Source checkouts reload on selection; installed/mobile builds use embedded data. @@ -74,15 +76,122 @@ pub fn load_sheet(style: DesktopStyle, dark: bool) -> StyleSheet { name: if dark { "macos-dark" } else { "macos" }.into(), theme: read(theme_name, theme), widgets: read(widgets_name, widgets), - icons: app_icon::load_assets(UpstreamStyle::Macos), + icons: icon_assets(UpstreamStyle::Macos), } } +/// The framework's artwork for a style, plus what this shell's apps need: +/// App Hub's own store icon, and for Maps (a system app the framework has no +/// art for) the route app's map, which it is the successor of. News, Photos +/// and Mail keep the framework's art; a system app whose bundle ships an +/// icon (Camera) draws that instead (`InstalledIcons`). +pub fn icon_assets(style: UpstreamStyle) -> Vec { + fn wear(assets: &mut Vec, name: &str, svg: String) { + match assets.iter_mut().find(|asset| asset.name == name) { + Some(asset) => asset.svg = svg, + None => assets.push(app_icon::IconAsset { name: name.into(), svg }), + } + } + let mut assets = app_icon::load_assets(style); + if !assets.iter().any(|asset| asset.name == "maps") { + if let Some(route) = assets.iter().find(|asset| asset.name == "route").map(|asset| asset.svg.clone()) { + wear(&mut assets, "maps", route); + } + } + #[cfg(feature = "app-hub")] + wear(&mut assets, "apphub", octosense_app_hub_app::APP_ICON_SVG.into()); + assets.sort_by(|a, b| a.name.cmp(&b.name)); + assets +} + #[derive(Default)] -pub struct AppIconDraw(app_icon::AppIconDraw); +pub struct AppIconDraw { + draw: app_icon::AppIconDraw, + #[cfg(feature = "app-hub")] + library: InstalledIcons, + /// The styles whose icon catalog this drawer has seen to, by discriminant. + installed: [bool; UpstreamStyle::ALL.len()], +} impl AppIconDraw { pub fn draw(&mut self, cx: &mut Cx2d, name: &str, style: DesktopStyle, rect: Rect, opacity: f32, ink: Vec4f) { - self.0.draw(cx, name, style.framework(), rect, opacity, ink); + #[cfg(feature = "app-hub")] + if self.library.draw(cx, name, rect, opacity) { + return; + } + let style = style.framework(); + // A style can be drawn before its sheet is applied (a crossfade's + // target, the first frame); the framework would then fall back to + // its own artwork, which has no Maps or App Hub. + if !std::mem::replace(&mut self.installed[style as usize], true) { + app_icon::install(cx, style, &icon_assets(style)); + } + self.draw.draw(cx, name, style, rect, opacity, ink); + } +} + +/// Launcher art App Hub owns: an installed app's icon (`hub:`), and a +/// system app's own art when its bundle ships one (ADR 0004). +#[cfg(feature = "app-hub")] +#[derive(Default)] +struct InstalledIcons { + root: Option, + generation: u64, + entries: std::collections::HashMap>, +} +#[cfg(feature = "app-hub")] +enum InstalledIcon { + Svg(DrawSvg), + Png(DrawImage, Texture), +} + +#[cfg(feature = "app-hub")] +impl InstalledIcons { + fn draw(&mut self, cx: &mut Cx2d, name: &str, rect: Rect, opacity: f32) -> bool { + use octosense_app_hub_app::icons::{self, IconData}; + let system = name.strip_prefix("hub:").is_none() && crate::apps::system_card_apps().iter().any(|a| a.id == name); + let Some(id) = name.strip_prefix("hub:").or(system.then_some(name)) else { return false; }; + let Some(root) = octosense_app_hub_app::data_root_if_set() else { return false; }; + let generation = icons::generation(); + if self.root.as_ref() != Some(&root) || self.generation != generation { + self.entries.clear(); + self.root = Some(root.clone()); + self.generation = generation; + } + if self.entries.len() >= 256 && !self.entries.contains_key(name) { + self.entries.clear(); + } + let icon = self.entries.entry(name.into()).or_insert_with(|| { + let data = if system { octosense_app_hub_app::system_icon(id)? } else { icons::read_installed_icon(&root, id)? }; + match data { + IconData::Svg(source) => { + let mut draw = cx.with_vm(|vm| DrawSvg::script_new_with_default(vm)); + draw.load_from_str(&source); + let (width, height) = draw.svg_doc.as_ref()?.logical_size(); + draw.content_bounds = (0.0, 0.0, width, height); + Some(InstalledIcon::Svg(draw)) + } + IconData::Png(data) => { + let buffer = image_cache::ImageBuffer::from_png(&data).ok()?; + let texture = buffer.into_new_texture(cx); + let draw = cx.with_vm(|vm| DrawImage::script_new_with_default(vm)); + Some(InstalledIcon::Png(draw, texture)) + } + } + }); + match icon { + Some(InstalledIcon::Svg(draw)) => { + draw.color = vec4(-1.0, -1.0, -1.0, -1.0); + draw.opacity = opacity; + draw.draw_abs(cx, rect); + } + Some(InstalledIcon::Png(draw, texture)) => { + draw.draw_vars.set_texture(0, texture); + draw.opacity = opacity; + draw.draw_abs(cx, rect); + } + None => return false, + } + true } } @@ -102,7 +211,7 @@ mod tests { assert_eq!(sheet.name, if dark { "macos-dark" } else { "macos" }); assert_eq!(StyleSheet::parse(&sheet.to_json()), Some(sheet.clone())); assert_eq!(UpstreamStyle::parse(&sheet.name), Some(UpstreamStyle::Macos)); - assert_eq!(sheet.icons, app_icon::load_assets(UpstreamStyle::Macos)); + assert_eq!(sheet.icons, icon_assets(UpstreamStyle::Macos)); desktop_style::install(vm, sheet); vm.bx.captured_errors = Some(Vec::new()); vm.with_reload(makepad_widgets::script_mod); diff --git a/src/shell/launcher.rs b/src/shell/launcher.rs index cf31e498..67cb8edb 100644 --- a/src/shell/launcher.rs +++ b/src/shell/launcher.rs @@ -56,7 +56,9 @@ fn icon_for(id: &str) -> Option { "image" => Ico::Monitor, "video" => Ico::Play, "pdf" => Ico::Check, - "route" => Ico::Globe, + "route" | "maps" => Ico::Globe, + "apphub" => Ico::Menu, + "camera" => Ico::Photo, "mixer" => Ico::Speaker, "vj" => Ico::Headphone, "photos" => Ico::Photo, diff --git a/src/shell/menu.rs b/src/shell/menu.rs index df6bcbf6..2b293852 100644 --- a/src/shell/menu.rs +++ b/src/shell/menu.rs @@ -482,6 +482,18 @@ impl MenuModel { ] {items.push(MenuItem::new(id,label,kind).icon(icon));} } let available = launcher::apps(); + // Settings gains a submenu once the build ships the AI providers + // system app (`os.ai-providers`): Appearance, then AI providers. + if path.starts_with("start") && available.iter().any(|app| app.id == AI_PROVIDERS) { + items.push( + MenuItem::new("start.settings.style", "Appearance", MenuKind::Menu) + .icon(Ico::Monitor), + ); + items.push( + MenuItem::new("start.settings.ai-providers", "AI providers", MenuKind::App) + .icon(Ico::Cpu), + ); + } items.retain(|item| { let app = match item.id.as_str() { "workspace.files" | "start.documents" => "apps.files", @@ -727,7 +739,8 @@ impl MenuModel { MenuKind::Menu => { let target=match row.target.as_str() { "start.programs"|"start.search"|"start.run"|"workspace.apps"=>"apps", - "start.settings"=>"style", + "start.settings" if !self.items.iter().any(|item| item.id=="start.settings.ai-providers")=>"style", + "start.settings.style"=>"style", "workspace.desktop"=>"desktop", other=>other, }; @@ -739,12 +752,16 @@ impl MenuModel { "workspace.files" => "apps.files".into(), "workspace.tools.terminal" => "apps.terminal".into(), "workspace.tools.task" => "apps.task".into(), + "start.settings.ai-providers" => AI_PROVIDERS.into(), _ => row.target, }), } } } +/// The launcher row of the AI providers system app (`os.ai-providers`). +const AI_PROVIDERS: &str = "apps.ai-providers"; + /// `MenuModel.js` `matchesQuery` + `searchScore`, lower is better. /// /// Every whitespace term has to match the label, the leaf id or an alias by @@ -1588,6 +1605,29 @@ mod tests { assert_eq!(centered_card_top(smaller, 280.0, 8.0, Some(300.0)), 138.0); } + /// Start > Settings is Appearance alone until the build ships the AI + /// providers system app; then it is a submenu that also opens it. + #[test] + fn start_settings_offers_ai_providers_once_it_ships() { + let mut model = MenuModel::default(); + model.open_at("", MenuSkin::Menu); + model.descend("start"); + let ships = launcher::apps().iter().any(|app| app.id == AI_PROVIDERS); + model.sel = model.rows.iter().position(|r| r.target == "start.settings").unwrap(); + assert_eq!(model.activate(), None); + if ships { + assert_eq!(model.path, "start.settings"); + model.sel = model + .rows + .iter() + .position(|r| r.target == "start.settings.ai-providers") + .unwrap(); + assert_eq!(model.activate().as_deref(), Some(AI_PROVIDERS)); + } else { + assert_eq!(model.path, "style"); + } + } + #[test] fn navigating_between_menus_recenters_after_a_search() { let mut model = MenuModel::default(); diff --git a/system-apps.json b/system-apps.json new file mode 100644 index 00000000..933675f8 --- /dev/null +++ b/system-apps.json @@ -0,0 +1,6 @@ +{ + "schema": 1, + "source": "../OctoSense-System-Apps/apps", + "apps": ["news", "photos", "maps", "camera", "mail", "ai-providers"], + "assets": {} +} diff --git a/tools/android-kernel.py b/tools/android-kernel.py new file mode 100644 index 00000000..0a80baff --- /dev/null +++ b/tools/android-kernel.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Bundle OctoSense's octos kernel into an Android APK (Python 3.9+). + +The octos kernel is a shell service (feature `octos-core`, on in every +Android build): at run time it execs `liboctos.so serve --stdio` from the +APK's native lib dir, the only place an Android app may exec a binary from. +This tool cross-builds that binary from the one octos revision `Cargo.lock` +pins and runs the packager with it bundled: + + python3 tools/android-kernel.py --sdk \\ + -- cargo makepad android run -p octosense --release + +It checks octos out into `target/octos-kernel/src` (never a sibling you may +be working in), builds `octos` for aarch64-linux-android with the SDK's NDK +clang (API 33, `--no-default-features --features api,git,ast`) and runs the +command after `--` with `MAKEPAD_ANDROID_EXTRA_LIBS=liboctos.so=`. +`--kernel ` bundles a prebuilt aarch64-linux-android `octos` instead; +with no command it only builds and prints the kernel's path. +""" +import argparse +import glob +import os +from pathlib import Path +import re +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[1] +OCTOS_URL = "https://github.com/octos-org/octos.git" +TARGET = "aarch64-linux-android" +API = "33" +KERNEL_BUILD = ["-p", "octos-cli", "--bin", "octos", "--no-default-features", "--features", "api,git,ast"] + + +def octos_revision(lock=None): + """The one octos revision OctoSense links (Cargo.lock).""" + text = (lock or ROOT / "Cargo.lock").read_text() + match = re.search(r'name = "octos-cli"\nversion = "[^"]+"\nsource = "git\+https://github\.com/octos-org/octos\.git\?rev=([0-9a-f]{40})#', text) + if not match: + raise RuntimeError("Cargo.lock names no octos-cli from octos-org/octos: cannot tell which kernel to build") + return match[1] + + +def ndk_bin(sdk): + """The newest NDK's LLVM bin dir inside a cargo-makepad Android SDK dir.""" + found = sorted(glob.glob(str(sdk / "ndk/*/toolchains/llvm/prebuilt/*/bin")), + key=lambda p: [int(x) if x.isdigit() else x for x in re.split(r"[./]", p)]) + if not found: + raise RuntimeError(f"No NDK under {sdk}/ndk (cargo makepad android install-toolchain puts one there)") + return Path(found[-1]) + + +def build_env(bin_dir): + clang = bin_dir / f"{TARGET}{API}-clang" + return {"CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER": str(clang), + "CARGO_TARGET_AARCH64_LINUX_ANDROID_AR": str(bin_dir / "llvm-ar"), + "CC_aarch64_linux_android": str(clang), + "CXX_aarch64_linux_android": str(bin_dir / f"{TARGET}{API}-clang++"), + "AR_aarch64_linux_android": str(bin_dir / "llvm-ar"), + "RANLIB_aarch64_linux_android": str(bin_dir / "llvm-ranlib")} + + +def plan(revision, work, offline=False): + """(cwd, argv) steps that check out and build the kernel.""" + src = work / "src" + steps = [(ROOT, ["git", "init", "--quiet", str(src)])] + if not offline: + steps.append((src, ["git", "fetch", "--quiet", "--no-tags", "--depth=1", OCTOS_URL, revision])) + steps.append((src, ["git", "checkout", "--quiet", "--detach", revision])) + build = ["cargo", "build", "--locked", "--release", "--target", TARGET, *KERNEL_BUILD] + if offline: + build.append("--offline") + steps.append((src, build)) + return steps, work / "target" / TARGET / "release/octos" + + +def main(argv=None): + argv = sys.argv[1:] if argv is None else argv + command = [] + if "--" in argv: + i = argv.index("--") + argv, command = argv[:i], argv[i + 1:] + p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + p.add_argument("--sdk", type=Path, help="cargo-makepad's Android SDK dir (holds ndk/)") + p.add_argument("--kernel", type=Path, help="A prebuilt aarch64-linux-android octos to bundle") + p.add_argument("--offline", action="store_true") + args = p.parse_args(argv) + if args.kernel: + kernel = args.kernel.resolve() + if not kernel.is_file(): + p.error(f"no such kernel: {kernel}") + else: + if not args.sdk: + p.error("--sdk is required to build the kernel (or pass --kernel)") + work = ROOT / "target/octos-kernel" + steps, kernel = plan(octos_revision(), work, args.offline) + env = dict(os.environ, **build_env(ndk_bin(args.sdk.resolve()))) + env["CARGO_TARGET_DIR"] = str(work / "target") + for cwd, step in steps: + subprocess.run(step, cwd=cwd, env=env, check=True) + print(f"octos kernel: {kernel}", flush=True) + if command: + env = dict(os.environ, MAKEPAD_ANDROID_EXTRA_LIBS=f"liboctos.so={kernel}") + sys.exit(subprocess.run(command, cwd=ROOT, env=env).returncode) + + +if __name__ == "__main__": + main() diff --git a/tools/setup-native.py b/tools/setup-native.py index 8e03b9d7..555162a0 100644 --- a/tools/setup-native.py +++ b/tools/setup-native.py @@ -1,59 +1,181 @@ #!/usr/bin/env python3 -"""Bootstrap an application's pinned Octoscript-Makepad runtime (Python 3.9+).""" +"""Prepare OctoSense's pinned sibling sources (Python 3.9+). + +Beside this repository, in one workspace directory: + + octoscript-makepad/ the release native-runtime.lock.json selects + makepad/, octoscript/ at the revisions that release's runtime.json pins, + plus any reviewed patch runtime-patches.lock.json + names (none today) + OctoSense-System-Apps/ at the revision native-apps.lock.json pins: the + system app bundles system-apps.json selects, the + Mail host service and the AppCard assistant + +Local changes are preserved; --update only moves clean checkouts. +""" import argparse +import hashlib import json +import os from pathlib import Path import re import subprocess -import sys CONSUMER = Path(__file__).resolve().parents[1] URL = "https://github.com/OctoSense-org/Octoscript-Makepad.git" +RUNTIME_URLS = { + "makepad": "https://github.com/OctoSense-org/makepad.git", + "octoscript": "https://github.com/OctoSense-org/Octoscript.git", +} +APP_SOURCES = { + # lock key: (sibling directory, URL) + "system-apps": ("OctoSense-System-Apps", "https://github.com/OctoSense-org/OctoSense-System-Apps.git"), +} def git(path, *args, check=True): - return subprocess.run(["git", "-C", str(path), *args], check=check, - capture_output=True, text=True) + result = subprocess.run(["git", "-C", str(path), *args], capture_output=True, text=True) + if check and result.returncode: + raise RuntimeError(result.stderr.strip() or f"git {' '.join(args)} failed in {path}") + return result + + +def fetch(path, name, revision, args): + if not git(path, "cat-file", "-e", revision + "^{commit}", check=False).returncode: + return + cached = args.cache.resolve() / name if args.cache else None + if cached and (cached / ".git").exists() and not git(cached, "cat-file", "-e", revision + "^{commit}", check=False).returncode: + if not git(path, "fetch", "--quiet", "--no-tags", "--depth=1", str(cached), revision, check=False).returncode: + return + git(path, "fetch", "--quiet", "--no-tags", "--depth=1", "origin", revision) + + +def prepare_source(root, name, url, revision, args, overlay=None): + """Check out `name` at `revision` (plus the reviewed `overlay` patch).""" + path = root / name + if not re.fullmatch(r"[0-9a-f]{40}", revision or ""): + raise RuntimeError(f"Unpinned source: {name}") + if not (path / ".git").exists(): + if args.check or path.exists() and any(path.iterdir()): + raise RuntimeError(f"Expected an empty dependency directory: {path}") + path.mkdir(parents=True, exist_ok=True) + git(path, "init", "--quiet") + git(path, "remote", "add", "origin", url) + if Path(git(path, "rev-parse", "--show-toplevel").stdout.strip()).resolve() != path.resolve(): + raise RuntimeError(f"Not a dependency checkout: {path}") + current = git(path, "rev-parse", "--verify", "HEAD", check=False).stdout.strip() + if current and (git(path, "diff", "--quiet", check=False).returncode + or git(path, "ls-files", "--others", "--exclude-standard").stdout): + raise RuntimeError(f"Preserving local changes: {path}") + tree = git(path, "write-tree").stdout.strip() if current else "" + head_tree = git(path, "rev-parse", "HEAD^{tree}", check=False).stdout.strip() + patch = None + if overlay: + patch = (CONSUMER / overlay["patch"]).resolve() + if (not patch.is_relative_to(CONSUMER) + or hashlib.sha256(patch.read_bytes()).hexdigest() != overlay["sha256"] + or overlay["base_revision"] != revision): + raise RuntimeError("Runtime patch does not match its source lock") + # Prepared: the base with the patch staged, or a clean checkout of the + # commit the patch was cut from (the same tree). + if tree == overlay["tree"] and (current == revision or head_tree == overlay["tree"]): + return current + elif current == revision and tree == head_tree: + return current + if current and tree != head_tree: + raise RuntimeError(f"Preserving staged changes: {path}") + if current != revision: + if args.check or current and not args.update: + raise RuntimeError(f"{path} selects another revision; --update only changes clean checkouts") + fetch(path, name, revision, args) + git(path, "checkout", "--quiet", "--detach", revision) + if patch: + if args.check: + raise RuntimeError(f"Reviewed runtime patch is not applied in {path}; run setup without --check") + git(path, "apply", "--check", str(patch)) + git(path, "apply", "--index", str(patch)) + if git(path, "write-tree").stdout.strip() != overlay["tree"]: + raise RuntimeError(f"Runtime patch produced an unexpected tree: {path}") + return revision + + +def verify_consumer(expected): + """Reject manifests that pin a runtime source at another revision.""" + for directory, subdirs, files in os.walk(CONSUMER): + subdirs[:] = [n for n in subdirs if n not in {".git", "target", "node_modules", "build", ".gradle"}] + if "Cargo.toml" not in files: + continue + path = Path(directory) / "Cargo.toml" + for line in path.read_text().splitlines(): + if line.lstrip().startswith("#"): + continue + url = re.search(r'git\s*=\s*"([^"]+)"', line) + if url and url[1] in expected: + rev = re.search(r'rev\s*=\s*"([0-9a-f]{40})"', line) + if not rev or rev[1] != expected[url[1]]: + raise RuntimeError(f"Divergent runtime dependency: {path}: {line}") + + +def verify_cargo(root, cargo_manifest): + """One Makepad in the graph, from the prepared sibling checkout.""" + metadata = json.loads(subprocess.check_output( + ["cargo", "metadata", "--locked", "--format-version", "1", "--features", "mobile-apps", + "--manifest-path", str(cargo_manifest)], cwd=cargo_manifest.parent, text=True)) + seen = set() + critical = {"makepad-script", "makepad-platform", "makepad-draw", "makepad-widgets", "makepad-live-id"} + for package in metadata["packages"]: + if package["name"] in critical: + if package["name"] in seen or not Path(package["manifest_path"]).resolve().is_relative_to(root / "makepad"): + raise RuntimeError(f"Duplicate or foreign runtime crate: {package['name']}") + seen.add(package["name"]) + if seen != critical: + raise RuntimeError("Incomplete Makepad dependency graph") + octos = {package["source"] for package in metadata["packages"] if package["name"] == "octos-core"} + if len(octos) > 1: + raise RuntimeError(f"More than one octos source: {sorted(octos)}") def main(): - parser = argparse.ArgumentParser(description=__doc__) + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--root", type=Path, default=CONSUMER.parent) - parser.add_argument("--update", action="store_true", help="Update clean checkouts to the release") - parser.add_argument("--check", action="store_true") - parser.add_argument("--cargo-manifest", type=Path) + parser.add_argument("--update", action="store_true", help="Move clean checkouts to the locked revisions") + parser.add_argument("--check", action="store_true", help="Verify without changing anything") + parser.add_argument("--cache", type=Path, help="Optional directory of Git object caches") + parser.add_argument("--cargo-manifest", type=Path, help="Also check the locked Cargo graph") args = parser.parse_args() root = args.root.resolve() if root == CONSUMER or root.is_relative_to(CONSUMER): - raise RuntimeError("Keep native framework repositories outside the application") + parser.error("Keep the sibling repositories outside the application") lock = json.loads((CONSUMER / "native-runtime.lock.json").read_text()) - if lock.get("schema_version") != 1 or lock.get("url") != URL or not re.fullmatch(r"[0-9a-f]{40}", lock.get("revision", "")): - raise RuntimeError("Expected a pinned OctoSense-org/Octoscript-Makepad release") - runtime = root / "octoscript-makepad" - if not (runtime / ".git").exists(): - if args.check or runtime.exists() and any(runtime.iterdir()): - raise RuntimeError(f"Prepare the runtime in an empty workspace: {runtime}") - runtime.mkdir(parents=True, exist_ok=True) - git(runtime, "init", "--quiet") - git(runtime, "remote", "add", "origin", URL) - current = git(runtime, "rev-parse", "--verify", "HEAD", check=False).stdout.strip() - for name in ("octoscript-makepad", "makepad", "octoscript"): - path = root / name - if (path / ".git").exists() and git(path, "status", "--porcelain", "--untracked-files=normal").stdout: - raise RuntimeError(f"Preserving local changes: {path}") - if current != lock["revision"]: - if args.check or current and not args.update: - raise RuntimeError("Another runtime is checked out; use --update with clean sources") - if git(runtime, "cat-file", "-e", lock["revision"] + "^{commit}", check=False).returncode: - git(runtime, "fetch", "--quiet", "--no-tags", "--depth=1", "origin", lock["revision"]) - git(runtime, "checkout", "--quiet", "--detach", lock["revision"]) - command = [sys.executable, str(runtime / "tools/runtime.py"), - "verify" if args.check else "prepare", "--root", str(root), "--consumer", str(CONSUMER)] - if args.update: - command.append("--update") + if lock.get("schema_version") != 1 or lock.get("url") != URL: + parser.error("Expected a pinned OctoSense-org/Octoscript-Makepad release") + patches = json.loads((CONSUMER / "runtime-patches.lock.json").read_text()) + if patches.get("schema_version") != 1 or not set(patches) <= {"schema_version", "makepad"}: + parser.error("Unsupported runtime patch lock") + apps = json.loads((CONSUMER / "native-apps.lock.json").read_text()) + if apps.get("schema_version") != 1 or set(apps.get("repositories", {})) != set(APP_SOURCES): + parser.error("Unsupported application source lock") + + prepare_source(root, "octoscript-makepad", URL, lock["revision"], args) + manifest = json.loads((root / "octoscript-makepad/runtime.json").read_text()) + if manifest.get("schema_version") != 1 or set(manifest.get("repositories", {})) != set(RUNTIME_URLS): + parser.error("Unsupported runtime source set") + expected = {URL: lock["revision"]} + for name, spec in manifest["repositories"].items(): + if spec.get("url") != RUNTIME_URLS[name]: + parser.error(f"Unexpected runtime source: {name}") + prepare_source(root, name, spec["url"], spec["revision"], args, patches.get(name)) + expected[spec["url"]] = spec["revision"] + for key, spec in apps["repositories"].items(): + directory, url = APP_SOURCES[key] + if spec.get("url") != url: + parser.error(f"Unexpected application source: {key}") + prepare_source(root, directory, url, spec["revision"], args) + verify_consumer(expected) if args.cargo_manifest: - command += ["--cargo-manifest", str(args.cargo_manifest.resolve())] - subprocess.run(command, check=True) + verify_cargo(root, args.cargo_manifest.resolve()) + print(json.dumps({"runtime": lock, "repositories": manifest["repositories"], + "patches": patches, "apps": apps["repositories"]}, indent=2)) if __name__ == "__main__": diff --git a/tools/test_android_kernel.py b/tools/test_android_kernel.py new file mode 100644 index 00000000..f588dd77 --- /dev/null +++ b/tools/test_android_kernel.py @@ -0,0 +1,46 @@ +"""Tests for tools/android-kernel.py (no network, no build).""" +import importlib.util +from pathlib import Path +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location("android_kernel", ROOT / "tools/android-kernel.py") +kernel = importlib.util.module_from_spec(spec) +spec.loader.exec_module(kernel) + + +class AndroidKernelTests(unittest.TestCase): + def test_the_revision_is_the_one_cargo_lock_pins(self): + rev = kernel.octos_revision() + self.assertRegex(rev, r"^[0-9a-f]{40}$") + with tempfile.TemporaryDirectory() as temp: + lock = Path(temp) / "Cargo.lock" + lock.write_text('[[package]]\nname = "serde"\n') + with self.assertRaises(RuntimeError): + kernel.octos_revision(lock) + + def test_the_plan_checks_out_and_cross_builds_the_kernel(self): + work = Path("/w") + steps, binary = kernel.plan("a" * 40, work) + commands = [c for _, c in steps] + self.assertIn(["git", "fetch", "--quiet", "--no-tags", "--depth=1", kernel.OCTOS_URL, "a" * 40], commands) + self.assertEqual(commands[-1], ["cargo", "build", "--locked", "--release", "--target", "aarch64-linux-android", *kernel.KERNEL_BUILD]) + self.assertEqual(binary, work / "target/aarch64-linux-android/release/octos") + offline, _ = kernel.plan("a" * 40, work, offline=True) + self.assertFalse(any(c[:2] == ["git", "fetch"] for _, c in offline)) + + def test_the_ndk_clang_is_found_in_the_sdk(self): + with tempfile.TemporaryDirectory() as temp: + sdk = Path(temp) + with self.assertRaises(RuntimeError): + kernel.ndk_bin(sdk) + bin_dir = sdk / "ndk/28.2.1/toolchains/llvm/prebuilt/darwin-x86_64/bin" + bin_dir.mkdir(parents=True) + self.assertEqual(kernel.ndk_bin(sdk), bin_dir) + env = kernel.build_env(bin_dir) + self.assertEqual(env["CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER"], str(bin_dir / "aarch64-linux-android33-clang")) + + +if __name__ == "__main__": + unittest.main()