From b4d5b1773fc0898cda685e6724113583a78a4c27 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 20:53:00 -0700 Subject: [PATCH 1/2] Hosted Rinx with the shell's assistant (Rinx ADR 0007) - Apps' assistant access: src/app_peers_host.rs gives each native module whose declared octos.* services host policy grants a scoped service from octosense-app-peers (System-Apps crates/app-peers), offered for the duration of its create and released at teardown. It is backed by ONE octos peer per app and account, owned by the system agent _main:api:octosense#system, on the shell's kernel. Modules without granted services get no peer. - Rinx (app-rinx, now in default) at hagency-org/Rinx with octosense-module only; app-rinx implies octos-core. CI checks that Rinx's standalone entry and local kernel stay out of the graph; System-Apps crates resolve to the sibling checkout (no URL aliases). - Runtime: OctoScript-Makepad c3d53ba8 (makepad db4691d0: Splash reapply_text, stateful mini-app inputs); System-Apps 4fa0f122 (app-peers). AppCard stays opt-in. Supersedes #41. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/runtime.yml | 11 +++ Cargo.lock | 50 +++++++--- Cargo.toml | 54 +++++++---- apps/appcard/Cargo.toml | 4 +- apps/reference/Cargo.toml | 4 +- native-apps.lock.json | 2 +- native-runtime.lock.json | 2 +- src/app_peers_host.rs | 170 ++++++++++++++++++++++++++++++++++ src/apps.rs | 7 ++ src/main.rs | 1 + src/module_host.rs | 17 ++++ 11 files changed, 286 insertions(+), 36 deletions(-) create mode 100644 src/app_peers_host.rs diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml index c39c4578..684e7da7 100644 --- a/.github/workflows/runtime.yml +++ b/.github/workflows/runtime.yml @@ -36,6 +36,17 @@ jobs: 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 diff --git a/Cargo.lock b/Cargo.lock index 83aba8c4..9c66dd16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" +source = "git+https://github.com/hagency-org/Rinx.git?rev=82391adf228692222462fc8696a04906a7aaf3eb#82391adf228692222462fc8696a04906a7aaf3eb" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" +source = "git+https://github.com/hagency-org/Rinx.git?rev=82391adf228692222462fc8696a04906a7aaf3eb#82391adf228692222462fc8696a04906a7aaf3eb" dependencies = [ "article-core", "libc", @@ -5718,7 +5718,7 @@ dependencies = [ [[package]] name = "matrix-sdk" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "anymap2", "as_variant", @@ -5776,7 +5776,7 @@ dependencies = [ [[package]] name = "matrix-sdk-base" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "as_variant", "async-trait", @@ -5803,7 +5803,7 @@ dependencies = [ [[package]] name = "matrix-sdk-common" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "eyeball-im", "futures-core", @@ -5826,7 +5826,7 @@ dependencies = [ [[package]] name = "matrix-sdk-crypto" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "aes 0.9.3", "as_variant", @@ -5865,7 +5865,7 @@ dependencies = [ [[package]] name = "matrix-sdk-indexeddb" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "async-trait", "base64 0.23.1", @@ -5897,7 +5897,7 @@ dependencies = [ [[package]] name = "matrix-sdk-sqlite" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "as_variant", "async-trait", @@ -5924,7 +5924,7 @@ dependencies = [ [[package]] name = "matrix-sdk-store-encryption" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "base64 0.23.1", "blake3", @@ -5946,7 +5946,7 @@ dependencies = [ [[package]] name = "matrix-sdk-ui" version = "0.18.0" -source = "git+https://github.com/matrix-org/matrix-rust-sdk#6892cb217ae4a886571e928c8efcccfbec5490a6" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=6892cb217ae4a886571e928c8efcccfbec5490a6#6892cb217ae4a886571e928c8efcccfbec5490a6" dependencies = [ "as_variant", "async-rx", @@ -7163,6 +7163,7 @@ dependencies = [ "makepad-wm-api", "makepad-wm-theme", "octosense-app-hub-app", + "octosense-app-peers", "octosense-appcard", "octosense-llm-config", "octosense-llm-service", @@ -7202,6 +7203,17 @@ dependencies = [ "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" @@ -8425,7 +8437,7 @@ dependencies = [ [[package]] name = "rinx" version = "1.0.0-beta.1" -source = "git+https://github.com/upstreamlabs/Rinx?rev=5ad2c1739e5b4776588518ab76440a5367c9968e#5ad2c1739e5b4776588518ab76440a5367c9968e" +source = "git+https://github.com/hagency-org/Rinx.git?rev=82391adf228692222462fc8696a04906a7aaf3eb#82391adf228692222462fc8696a04906a7aaf3eb" dependencies = [ "anyhow", "arboard", @@ -8461,10 +8473,16 @@ dependencies = [ "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", @@ -8483,10 +8501,20 @@ dependencies = [ "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=82391adf228692222462fc8696a04906a7aaf3eb#82391adf228692222462fc8696a04906a7aaf3eb" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "rmcp" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index af6d1963..bbeb645e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,19 +40,19 @@ 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 = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", features = ["appcard", "maps"] } -makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", features = ["appcard", "maps"] } +makepad-studio-protocol = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-network = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } # 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 = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } # 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 = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false, optional = true } -makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false, optional = true } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false, optional = true } +makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", 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. @@ -84,13 +84,20 @@ octosense-llm-config = { path = "../OctoSense-System-Apps/apps/ai-providers/conf # 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 } -# Rinx (the Matrix client with Moments and articles) as an in-process module. -# Built without its standalone window entry point. -rinx = { git = "https://github.com/upstreamlabs/Rinx", rev = "5ad2c1739e5b4776588518ab76440a5367c9968e", default-features = false, features = ["octosense-module"], 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 = "82391adf228692222462fc8696a04906a7aaf3eb", 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 = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false, optional = true } +makepad-aichat = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", 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. @@ -99,13 +106,14 @@ octosense-reference = { path = "apps/reference" } # 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" } -makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", default-features = false } +octosense-app-peers = { path = "../OctoSense-System-Apps/crates/app-peers", features = ["octos-core"] } +makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", default-features = false } [features] # 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"] +default = ["app-hub", "octos-core", "app-rinx"] # Also usable on desktop for testing the same embedded apps with --module. # 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). @@ -123,7 +131,7 @@ app-reference = ["dep:octosense-reference"] app-sheets = ["dep:makepad-sheets"] # Photos (the picture wall) linked in as a module, the same way. app-photos = ["dep:makepad-photos"] -octos-core = ["dep:octosense-octos-core", "octosense-llm-service?/octos-core"] +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 @@ -132,8 +140,9 @@ 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 (`--module rinx`). -app-rinx = ["dep:rinx"] +# 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"] @@ -196,6 +205,13 @@ 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/apps/appcard/Cargo.toml b/apps/appcard/Cargo.toml index d2774c6c..d83b8d8b 100644 --- a/apps/appcard/Cargo.toml +++ b/apps/appcard/Cargo.toml @@ -6,8 +6,8 @@ license = "Apache-2.0" publish = false [dependencies] -makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d", features = ["appcard", "maps"] } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f", features = ["appcard", "maps"] } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } # 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 diff --git a/apps/reference/Cargo.toml b/apps/reference/Cargo.toml index c6252141..fd7f1dbc 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 = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } -makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "d0a9def5fc554a52955db21a2a5a196daccc8e3d" } +makepad-widgets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } +makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "db4691d0e07dc9d1e498f0430ecfaadc4ae15f8f" } diff --git a/native-apps.lock.json b/native-apps.lock.json index e13c4602..f6e3b6fb 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "d30c67cb8f470f4b0c9259c091a7efeae5098205" + "revision": "4fa0f122ab01d58e468d339f1920ec2758741012" } } } diff --git a/native-runtime.lock.json b/native-runtime.lock.json index 993be108..ec3be363 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": "99c1e5ee7925060ced59c66d6f8763c22217e4ad" + "revision": "c3d53ba8bdbc219478b98b34fc73066228d9dfb3" } 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 48d05d5f..3670ad7f 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -383,6 +383,7 @@ 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(); // AppCard is opt-in (`app-appcard`), not part of `mobile-apps`. let expected: &[&str] = if cfg!(feature = "app-appcard") { @@ -390,6 +391,12 @@ mod tests { } 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())); // The system apps have no native module: the Card runner hosts them, diff --git a/src/main.rs b/src/main.rs index 35dd0fe2..ce6d5529 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; diff --git a/src/module_host.rs b/src/module_host.rs index 59171e9d..1b6a3d7e 100644 --- a/src/module_host.rs +++ b/src/module_host.rs @@ -40,6 +40,9 @@ pub struct AppInstance { 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 { @@ -119,6 +122,9 @@ impl ModuleHost { 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. @@ -129,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(), @@ -150,6 +157,7 @@ impl ModuleHost { shutdown: Some(parts.shutdown), upstream, windows, + assistant, }, ); Ok(()) @@ -171,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) } @@ -284,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. From 56f18aa4c661b0ea8f4f0cc37bb768f60e18afcc Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:21:59 -0700 Subject: [PATCH 2/2] System-Apps cc1cee3e: the kernel with host-owned app peers (octos 552767dd) Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 32 ++++++++++++++++---------------- native-apps.lock.json | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c66dd16..d75c50a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6646,7 +6646,7 @@ dependencies = [ [[package]] name = "octos-agent" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "base64 0.22.1", @@ -6774,7 +6774,7 @@ dependencies = [ [[package]] name = "octos-bus" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "aes 0.8.4", "async-trait", @@ -6803,7 +6803,7 @@ dependencies = [ [[package]] name = "octos-cli" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "agent-client-protocol", "argon2", @@ -6883,7 +6883,7 @@ dependencies = [ [[package]] name = "octos-core" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "chrono", "eyre", @@ -6897,7 +6897,7 @@ dependencies = [ [[package]] name = "octos-diagnostics" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "eyre", "octos-core", @@ -6909,7 +6909,7 @@ dependencies = [ [[package]] name = "octos-fleet" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "eyre", "octos-core", @@ -6925,7 +6925,7 @@ dependencies = [ [[package]] name = "octos-fleet-worker" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "eyre", @@ -6942,7 +6942,7 @@ dependencies = [ [[package]] name = "octos-llm" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "base64 0.22.1", @@ -6966,7 +6966,7 @@ dependencies = [ [[package]] name = "octos-memory" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "bincode", "chrono", @@ -6985,7 +6985,7 @@ dependencies = [ [[package]] name = "octos-pipeline" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "chrono", @@ -7007,7 +7007,7 @@ dependencies = [ [[package]] name = "octos-plugin" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "eyre", "metrics", @@ -7021,7 +7021,7 @@ dependencies = [ [[package]] name = "octos-sandbox" version = "0.1.0" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "clap", "eyre", @@ -7036,7 +7036,7 @@ dependencies = [ [[package]] name = "octos-services" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "chrono", "dirs", @@ -7057,7 +7057,7 @@ dependencies = [ [[package]] name = "octos-store" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "base64 0.22.1", "chrono", @@ -7077,7 +7077,7 @@ dependencies = [ [[package]] name = "octos-swarm" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "async-trait", "chrono", @@ -7096,7 +7096,7 @@ dependencies = [ [[package]] name = "octos-workflows" version = "2.0.3-rc.13" -source = "git+https://github.com/octos-org/octos.git?rev=6ad76e5c1e659bdf10ec05ae869428b48edccf7f#6ad76e5c1e659bdf10ec05ae869428b48edccf7f" +source = "git+https://github.com/octos-org/octos.git?rev=552767dd50e16e08c6225a0cb252fe884e794084#552767dd50e16e08c6225a0cb252fe884e794084" dependencies = [ "chrono", "eyre", diff --git a/native-apps.lock.json b/native-apps.lock.json index f6e3b6fb..6e66a5d8 100644 --- a/native-apps.lock.json +++ b/native-apps.lock.json @@ -3,7 +3,7 @@ "repositories": { "system-apps": { "url": "https://github.com/OctoSense-org/OctoSense-System-Apps.git", - "revision": "4fa0f122ab01d58e468d339f1920ec2758741012" + "revision": "cc1cee3ee308843aac569111cf2eb31f995646cd" } } }