mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 21:16:52 -06:00
71 lines
3.2 KiB
Rust
71 lines
3.2 KiB
Rust
use super::{EngineError, Event, Result, Session, Value, commands, guard};
|
|
|
|
impl Session {
|
|
pub fn new() -> Session {
|
|
Session::default()
|
|
}
|
|
|
|
/// Run a command by id.
|
|
pub fn execute(&mut self, id: &str, params: Value) -> Result<Value> {
|
|
let spec = commands::find(id).ok_or_else(|| EngineError::UnknownCommand(id.to_string()))?;
|
|
if let Err(why) = (spec.enabled)(self) {
|
|
// Explicit targets (agents, scripts) don't need a UI selection.
|
|
let explicit = (["layer", "layers", "prop", "keys"].iter().any(|k| params.get(k).is_some()) && commands::has_comp(self).is_ok())
|
|
|| ["item", "items"].iter().any(|k| params.get(k).is_some())
|
|
// An explicit composition (the enablement looks at the active one).
|
|
|| params.get("comp").is_some_and(|c| !c.is_null() && self.resolve_comp(Some(c)).is_ok());
|
|
if !explicit {
|
|
return Err(EngineError::Disabled(id.to_string(), why));
|
|
}
|
|
}
|
|
// Last-resort guard: a panicking command reports an error instead of crashing the app.
|
|
let r = guard::guarded(&format!("command `{id}`"), || (spec.run)(self, ¶ms))?;
|
|
self.learn_observe(id, ¶ms);
|
|
if spec.journal {
|
|
self.journal.push((id.to_string(), params));
|
|
if self.journal.len() > 10_000 {
|
|
self.journal.drain(..1000);
|
|
}
|
|
}
|
|
Ok(r)
|
|
}
|
|
|
|
/// [`Session::execute`] for agents and scripts (MCP, CLI, control channel): unknown top-level
|
|
/// parameters are rejected with the accepted keys instead of being silently ignored.
|
|
pub fn execute_checked(&mut self, id: &str, params: Value) -> Result<Value> {
|
|
let spec = commands::find(id).ok_or_else(|| EngineError::UnknownCommand(id.to_string()))?;
|
|
commands::check_params(spec, ¶ms)?;
|
|
self.execute(id, params)
|
|
}
|
|
|
|
/// [`Session::execute`] for a command a script asked for (the JavaScript engine's
|
|
/// `app.run`, ScriptUI callbacks…). The command and everything it runs count as the script's:
|
|
/// it may not change the settings that limit scripts (Settings ▸ Scripting & Expressions ▸
|
|
/// Application Scripting), which only the user can, from the UI, CLI or control channel.
|
|
pub fn execute_for_script(&mut self, id: &str, params: Value) -> Result<Value> {
|
|
self.script_calls.0 = self.script_calls.0.saturating_add(1);
|
|
let r = self.execute(id, params);
|
|
self.script_calls.0 = self.script_calls.0.saturating_sub(1);
|
|
r
|
|
}
|
|
|
|
/// Whether the command running was issued by a script ([`Session::execute_for_script`]).
|
|
pub fn running_for_script(&self) -> bool {
|
|
self.script_calls.0 > 0
|
|
}
|
|
|
|
/// Whether `id` names a known command whose `enabled` predicate returns `Ok` for this session;
|
|
/// an unknown id or a predicate error reads as disabled.
|
|
pub fn is_enabled(&self, id: &str) -> bool {
|
|
commands::find(id).is_some_and(|c| (c.enabled)(self).is_ok())
|
|
}
|
|
|
|
pub fn toast(&mut self, msg: impl Into<String>) {
|
|
self.events.push(Event::Toast { message: msg.into(), error: false });
|
|
}
|
|
|
|
pub fn drain_events(&mut self) -> Vec<Event> {
|
|
std::mem::take(&mut self.events)
|
|
}
|
|
}
|