Repository navigation
Extensions system #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
cxnmai
wants to merge
54
commits into
main
Choose a base branch
from
extensions-system
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
0641371
Add extension manifest discovery and trust model
cxnmai 85b5a48
Add extension panel view model seam
cxnmai 284f72f
Add editor extension API types
cxnmai 087fd34
Wire extension panel controller into workspace
cxnmai e4d2ce1
Add Extensions tool to workspace rail
cxnmai 83f0009
Add extension panel interactions and trust prompt
cxnmai b0cbf98
Expose extension document snapshots
cxnmai 711b0f7
Apply extension edits transactionally
cxnmai 988766d
Define extension component interface
cxnmai 8002fc3
Add Wasmtime extension runtime
cxnmai 122f592
Reset editor lifecycle on disk refresh
cxnmai 89d0b55
Test extension edit validation
cxnmai 1d5d065
Fix extension API imports
cxnmai fe0c76a
Test extension validation and coordination
cxnmai 8bd17e9
Render extension action panel
cxnmai e00568c
Box replacement documents in extension edits
cxnmai 716d50c
Satisfy extension crate test lint
cxnmai c5a35e5
Enable WASI HTTP for trusted extensions
cxnmai 72f3739
Fix extension panel integration
cxnmai 33abc92
Document extension authoring and capabilities
cxnmai 7c9a07d
Cache compiled extension components by digest
cxnmai ec41958
Add extension JSON wire types
cxnmai 838254b
Support structural block splices
cxnmai d9a7e1c
Expose equation source selections
cxnmai ef51a74
Bridge extension editor APIs through JSON
cxnmai d3324d0
Test extension history and block splices
cxnmai 68561a9
Keep extension tests independent of GPUI harness
cxnmai 36ea98e
Test extension JSON edit conversion
cxnmai c333e22
Add deferred directory grants to extension API
cxnmai 99940c0
Exercise component invocation and host callback
cxnmai e590de2
Verify cancellation against running component
cxnmai b8a4e18
Tighten component cache lock scopes
cxnmai 73cd50b
Connect extension discovery runtime and trust
cxnmai ee41f08
Add bounded extension editor host bridge
cxnmai 566d33c
Bridge extension actions to active editor
cxnmai 3f6af76
Harden extension grants refresh and events
cxnmai c314a34
Scope document directory to declared actions
cxnmai 0028a0b
Document extension directory grants
cxnmai 996e1b4
Add all-capabilities extension scaffold
cxnmai 396a09b
Add extension action dispatch and document edits
cxnmai d9df4a1
Demonstrate rich document block insertion
cxnmai c18d416
Exercise WASI and directory capabilities
cxnmai 1b64127
Cover destructive extension actions
cxnmai 77160bf
Finalize guest bindings and runtime demos
cxnmai f70ed31
Persist and exercise directory grants
cxnmai d15a492
Handle normalized selection edits and tests
cxnmai 89a9808
Document the capability example workflow
cxnmai ef6856e
Explain all-capabilities extension actions
cxnmai 19e7d4e
Link base WASI with extension HTTP support
cxnmai 209de19
fix(extensions): allow guest DNS lookup
cxnmai 490ed6f
Enable precise extension JSON numbers
cxnmai dc2ab4d
Decode asset IDs safely in extension edits
cxnmai bf92699
Emit string asset IDs in extension example
cxnmai 61e9f62
Derive extension asset hashes from bytes
cxnmai File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| [package] | ||
| name = "flowstate-extension" | ||
| version.workspace = true | ||
| edition.workspace = true | ||
| publish.workspace = true | ||
|
|
||
| [dependencies] | ||
| anyhow.workspace = true | ||
| bytes = "1.12.1" | ||
| hex = "0.4.3" | ||
| parking_lot = "0.12.5" | ||
| serde = { workspace = true, features = ["derive"] } | ||
| sha2 = "0.11.0" | ||
| thiserror = "2.0.18" | ||
| toml.workspace = true | ||
| wasmtime = { version = "38", features = ["component-model", "cranelift", "runtime"] } | ||
| wasmtime-wasi = "38" | ||
| wasmtime-wasi-http = "38" | ||
|
|
||
| [lints] | ||
| workspace = true | ||
|
|
||
| [dev-dependencies] | ||
| tempfile.workspace = true | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| use std::collections::HashSet; | ||
| use std::fs; | ||
| use std::path::{Path, PathBuf}; | ||
|
|
||
| use crate::ExtensionManifest; | ||
|
|
||
| #[derive(Clone, Debug)] | ||
| pub struct InstalledExtension { | ||
| pub manifest: ExtensionManifest, | ||
| pub root: PathBuf, | ||
| pub component_path: PathBuf, | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, Eq, PartialEq)] | ||
| pub struct DiscoveryIssue { | ||
| pub path: PathBuf, | ||
| pub message: String, | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, Default)] | ||
| pub struct DiscoveryResult { | ||
| pub extensions: Vec<InstalledExtension>, | ||
| pub issues: Vec<DiscoveryIssue>, | ||
| } | ||
|
|
||
| pub fn discover_extensions(root: &Path) -> DiscoveryResult { | ||
| let mut result = DiscoveryResult::default(); | ||
| let Ok(entries) = fs::read_dir(root) else { return result }; | ||
| let mut manifests: Vec<_> = entries.flatten().map(|entry| entry.path().join("extension.toml")).filter(|path| path.is_file()).collect(); | ||
| manifests.sort(); | ||
| let mut ids = HashSet::new(); | ||
| for path in manifests { | ||
| match load_extension(&path) { | ||
| Ok(extension) if ids.insert(extension.manifest.id.clone()) => result.extensions.push(extension), | ||
| Ok(extension) => result.issues.push(DiscoveryIssue { path, message: format!("duplicate extension id: {}", extension.manifest.id) }), | ||
| Err(message) => result.issues.push(DiscoveryIssue { path, message }), | ||
| } | ||
| } | ||
| result | ||
| } | ||
|
|
||
| fn load_extension(path: &Path) -> Result<InstalledExtension, String> { | ||
| let source = fs::read_to_string(path).map_err(|error| error.to_string())?; | ||
| let manifest = ExtensionManifest::parse(&source).map_err(|error| error.to_string())?; | ||
| let root = path.parent().ok_or("manifest has no parent directory")?.to_path_buf(); | ||
| let component_path = root.join(&manifest.component); | ||
| if !component_path.is_file() { | ||
| return Err(format!("component does not exist: {}", component_path.display())); | ||
| } | ||
| let canonical_root = root.canonicalize().map_err(|error| error.to_string())?; | ||
| let canonical_component = component_path.canonicalize().map_err(|error| error.to_string())?; | ||
| if !canonical_component.starts_with(&canonical_root) { | ||
| return Err("component resolves outside the extension directory".to_owned()); | ||
| } | ||
| Ok(InstalledExtension { manifest, root, component_path: canonical_component }) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| mod discovery; | ||
| mod manifest; | ||
| mod runtime; | ||
| mod trust; | ||
|
|
||
| pub use discovery::{DiscoveryIssue, DiscoveryResult, InstalledExtension, discover_extensions}; | ||
| pub use manifest::{ActionManifest, ExtensionManifest, ManifestError}; | ||
| pub use runtime::{CancellationHandle, DirectoryAccess, DirectoryGrant, DirectoryGrantResponse, ExtensionHost, HostError, Invocation, InvocationOutput, Runtime, RuntimeConfig, RuntimeError}; | ||
| pub use trust::{ComponentDigest, TrustDecision, TrustStore}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| use std::collections::HashSet; | ||
| use std::path::{Component, Path}; | ||
|
|
||
| use serde::Deserialize; | ||
| use thiserror::Error; | ||
|
|
||
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] | ||
| #[serde(deny_unknown_fields)] | ||
| pub struct ExtensionManifest { | ||
| pub manifest_version: u32, | ||
| pub id: String, | ||
| pub name: String, | ||
| pub version: String, | ||
| pub component: String, | ||
| #[serde(default)] | ||
| pub actions: Vec<ActionManifest>, | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] | ||
| #[serde(deny_unknown_fields)] | ||
| pub struct ActionManifest { | ||
| pub id: String, | ||
| pub label: String, | ||
| #[serde(default)] | ||
| pub requires_document: bool, | ||
| } | ||
|
|
||
| #[derive(Debug, Error, Eq, PartialEq)] | ||
| pub enum ManifestError { | ||
| #[error("manifest TOML is invalid: {0}")] | ||
| Toml(String), | ||
| #[error("unsupported manifest version {0}")] | ||
| UnsupportedVersion(u32), | ||
| #[error("{field} must be a dot-separated identifier")] | ||
| InvalidId { field: &'static str }, | ||
| #[error("{field} must not be empty")] | ||
| Empty { field: &'static str }, | ||
| #[error("component must be a relative path without parent traversal")] | ||
| InvalidComponentPath, | ||
| #[error("duplicate action id: {0}")] | ||
| DuplicateAction(String), | ||
| #[error("at least one action is required")] | ||
| NoActions, | ||
| } | ||
|
|
||
| impl ExtensionManifest { | ||
| pub fn parse(source: &str) -> Result<Self, ManifestError> { | ||
| let manifest: Self = toml::from_str(source).map_err(|error| ManifestError::Toml(error.to_string()))?; | ||
| manifest.validate()?; | ||
| Ok(manifest) | ||
| } | ||
|
|
||
| pub fn validate(&self) -> Result<(), ManifestError> { | ||
| if self.manifest_version != 1 { | ||
| return Err(ManifestError::UnsupportedVersion(self.manifest_version)); | ||
| } | ||
| validate_id(&self.id, "id")?; | ||
| require_text(&self.name, "name")?; | ||
| require_text(&self.version, "version")?; | ||
| let path = Path::new(&self.component); | ||
| if path.is_absolute() || path.as_os_str().is_empty() || path.components().any(|part| matches!(part, Component::ParentDir | Component::RootDir | Component::Prefix(_))) { | ||
| return Err(ManifestError::InvalidComponentPath); | ||
| } | ||
| if self.actions.is_empty() { | ||
| return Err(ManifestError::NoActions); | ||
| } | ||
| let mut ids = HashSet::new(); | ||
| for action in &self.actions { | ||
| validate_id(&action.id, "action id")?; | ||
| require_text(&action.label, "action label")?; | ||
| if !ids.insert(&action.id) { | ||
| return Err(ManifestError::DuplicateAction(action.id.clone())); | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| fn require_text(value: &str, field: &'static str) -> Result<(), ManifestError> { | ||
| if value.trim().is_empty() { Err(ManifestError::Empty { field }) } else { Ok(()) } | ||
| } | ||
|
|
||
| fn validate_id(value: &str, field: &'static str) -> Result<(), ManifestError> { | ||
| let valid = !value.is_empty() && value.split('.').all(|part| !part.is_empty() && part.bytes().all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))); | ||
| if valid { Ok(()) } else { Err(ManifestError::InvalidId { field }) } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
What is the latest version of the wasmtime crate on crates.io as of July 2026, and are there any known security advisories for wasmtime version 38?💡 Result:
As of July 13, 2026, the latest version of the wasmtime crate available on crates.io is 45.0.1 [1][2]. Regarding wasmtime version 38, there are known security advisories associated with this release series: 1. GHSA-4h67-722j-5pmc (CVE-2025-62711): Versions 38.0.0, 38.0.1, and 38.0.2 contained a bug in the implementation of component-model related host-to-wasm trampolines. This vulnerability could allow a carefully crafted component to crash the host with a segfault or assertion failure [3][4]. This was patched in version 38.0.3 [3][4]. 2. GHSA-hc7m-r6v8-hg9q (RUSTSEC-2025-0118): This advisory identified an unsound API interaction where WebAssembly shared linear memory could be improperly accessed, potentially leading to data races. Versions up to and including 38.0.3 were affected [5][6]. This was addressed in version 38.0.4 [5][6].
Citations:
🏁 Script executed:
Repository: realartists-gitmo/flowstate
Length of output: 1691
🌐 Web query:
Does sha2 0.11.0 still expose theDigesttrait andSha256::digestAPI used in sha2 0.10.x? Also, what is the current latest 38.x patch release for wasmtime on crates.io?💡 Result:
Yes, the sha2 crate in version 0.11.0 continues to expose the Digest trait and the Sha256::digest API, maintaining compatibility with the one-shot API patterns used in version 0.10.x [1][2][3]. Regarding the wasmtime crate, the latest 38.x patch release available on crates.io is 38.0.4 [4][5].
Citations:
Bump the wasmtime family to 38.0.4+
wasmtime38.0.0-38.0.3 have known advisories; keepwasmtime,wasmtime-wasi, andwasmtime-wasi-httpon at least 38.0.4 (or a newer major). Thesha20.11.0Digest::digestcall intrust.rsis still valid.🤖 Prompt for AI Agents