Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
544 changes: 283 additions & 261 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ unicode-width = "0.2"
tinyagents = { path = "vendor/openhuman/vendor/tinyagents" }
tinychannels = { path = "vendor/openhuman/vendor/tinychannels" }
tinycortex = { path = "vendor/openhuman/vendor/tinycortex" }
# OpenHuman's extracted TinyMemory core names this contract by version. The
# vendored OpenHuman manifest patches it, but path dependencies do not inherit
# their own patch tables, so Medulla must bind it at this workspace root too.
tinycortex-api = { path = "vendor/openhuman/vendor/tinycortex/api" }
tinyflows = { path = "vendor/openhuman/vendor/tinyflows" }
tinyjuice = { path = "vendor/openhuman/vendor/tinyjuice" }
# Load-bearing even though no crate in this workspace links tinyplace any more:
Expand Down
11 changes: 7 additions & 4 deletions scripts/init-submodules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,18 @@ cd "$(dirname "$0")/.."
# The embedded OpenHuman core.
git submodule update --init --depth 1 vendor/openhuman

# Its vendored crates, which the root patch table redirects Cargo to. This list
# must stay in lockstep with the `[patch.crates-io]` table in Cargo.toml.
# Its vendored crates and direct path dependencies. The crates patched by the
# root manifest must stay in lockstep with its `[patch.crates-io]` table;
# tinybus and tinymemory are unpublished direct paths from OpenHuman's manifest.
git -C vendor/openhuman submodule update --init --depth 1 \
vendor/tinyagents \
vendor/tinybus \
vendor/tinychannels \
vendor/tinycortex \
vendor/tinyflows \
vendor/tinyhumans-sdk \
vendor/tinyjuice \
vendor/tinyplace
vendor/tinyplace \
vendor/tinymemory

echo "Submodules initialized (OpenHuman core + its seven vendored crates)."
echo "Submodules initialized (OpenHuman core + its nine required dependencies)."
6 changes: 6 additions & 0 deletions src/sdk/src/flow_engine/caps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ fn build_capabilities_inner(
services.http_credentials,
)),
code,
// TinyFlows owns the shell-node contract, but Medulla has not yet
// adapted its path, environment, and interpreter policy to that
// capability. Refuse shell nodes explicitly until that boundary exists
// rather than running an author-controlled command with the code
// runner's looser shape.
shell: None,
Comment thread
senamakel marked this conversation as resolved.
state: Arc::new(FileStateStore::new(&settings.state_dir, state_namespace)),
resolver: services.resolver,
// `None` until the host exposes a memory store: the engine then fails a
Expand Down
21 changes: 21 additions & 0 deletions src/sdk/src/flow_engine/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,27 @@ fn a_node_may_name_its_instruction_prompt_or_instruction() {
assert!(err.to_string().contains("prompt"), "got {err}");
}

#[test]
fn production_capabilities_refuse_shell_execution_until_its_policy_exists() {
let root = tempfile::tempdir().unwrap();
let caps = build_capabilities(
settings(root.path()),
HostServices {
node_progress: None,
dispatch: RecordingDispatch::replying("unused"),
resolver: empty_resolver(root.path()),
http_credentials: HashMap::new(),
},
"workflow:demo",
"run-shell-boundary",
);

assert!(
caps.shell.is_none(),
"shell nodes must remain unavailable until Medulla supplies path, environment, and interpreter policy"
);
}

#[test]
fn a_json_reply_is_surfaced_structurally_as_well_as_textually() {
let value = reply_to_value("{\"files\": 3}", "builder");
Expand Down
14 changes: 13 additions & 1 deletion src/sdk/src/ui/workflows/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ pub fn kind_wire(kind: &NodeKind) -> &'static str {
NodeKind::ToolCall => "tool_call",
NodeKind::HttpRequest => "http_request",
NodeKind::Code => "code",
NodeKind::Shell => "shell",
NodeKind::Condition => "condition",
NodeKind::Switch => "switch",
NodeKind::Merge => "merge",
Expand All @@ -467,6 +468,7 @@ pub fn kind_glyph(kind: &NodeKind) -> &'static str {
NodeKind::ToolCall => "⚒",
NodeKind::HttpRequest => "⇅",
NodeKind::Code => "λ",
NodeKind::Shell => "$",
NodeKind::Condition => "◆",
NodeKind::Switch => "⑂",
NodeKind::Merge => "⊕",
Expand Down Expand Up @@ -498,7 +500,11 @@ pub fn kind_color(kind: &NodeKind) -> &'static str {
NodeKind::Agent | NodeKind::SubWorkflow => "magenta",
// Grouped with the reaching-outside kinds: a memory node's result comes
// from the host's store, not from anything the graph carries.
NodeKind::ToolCall | NodeKind::HttpRequest | NodeKind::Code | NodeKind::Memory => "cyan",
NodeKind::ToolCall
| NodeKind::HttpRequest
| NodeKind::Code
| NodeKind::Shell
| NodeKind::Memory => "cyan",
// `dedup` reads durable state, but what it *does* to the graph is route:
// an item either continues or is dropped, so it reads with the control
// flow rather than with the kinds that reach outside the process.
Expand Down Expand Up @@ -554,6 +560,12 @@ pub fn node_summary(node: &Node) -> String {
}
}
NodeKind::Code => text("language").unwrap_or_default(),
NodeKind::Shell => text("script_path")
Comment thread
senamakel marked this conversation as resolved.
.or_else(|| {
text("source")
.map(|source| source.split('\n').next().unwrap_or(&source).to_string())
})
.unwrap_or_default(),
NodeKind::Condition => text("expression")
.or_else(|| text("left"))
.unwrap_or_default(),
Expand Down
22 changes: 22 additions & 0 deletions src/sdk/src/ui/workflows/graph_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ fn every_kind_has_a_wire_name_a_glyph_and_a_colour() {
NodeKind::ToolCall,
NodeKind::HttpRequest,
NodeKind::Code,
NodeKind::Shell,
Comment thread
senamakel marked this conversation as resolved.
NodeKind::Condition,
NodeKind::Switch,
NodeKind::Merge,
Expand All @@ -385,6 +386,10 @@ fn every_kind_has_a_wire_name_a_glyph_and_a_colour() {
kind_wire(&kind)
);
}

assert_eq!(kind_wire(&NodeKind::Shell), "shell");
assert_eq!(kind_glyph(&NodeKind::Shell), "$");
assert_eq!(kind_color(&NodeKind::Shell), "cyan");
}

#[test]
Expand All @@ -402,6 +407,23 @@ fn a_summary_names_what_identifies_the_node_for_its_kind() {
node("a", NodeKind::Agent, json!({"prompt":"first line\nsecond"})),
"first line",
),
(
node(
"s",
NodeKind::Shell,
json!({"script_path":"scripts/backup.sh","source":"echo ignored"}),
),
"scripts/backup.sh",
),
(
node(
"s-source",
NodeKind::Shell,
json!({"source":"echo first\necho second"}),
),
"echo first",
),
(node("s-empty", NodeKind::Shell, json!({})), ""),
(
node("c", NodeKind::Condition, json!({"expression":"=.ok"})),
"=.ok",
Expand Down
2 changes: 1 addition & 1 deletion vendor/openhuman
Submodule openhuman updated 1273 files
Loading