forked from 1b-agent/fieldcraft-upstream
Engine: each command lists what it needs, and execute checks it first
CommandSpec gains `requires: &[Requirement]` (crates/engine/src/requirement.rs): an open
design, geometry, nodes, a mesh, something to analyse, materials and sections for the
elements, sets, the definition an edit or delete acts on, a step, a result. Session::execute
checks them before running, so a command missing one fails at once with no partial work,
naming each missing piece and the toolbar entry that provides it ("Needs a material —
Properties › Material"). Requirements are real prerequisites (the model's own reference
checks and model.check's rules), never a workflow order. engine.commands / list_commands
list them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
06b98f01c7
commit
e0d5adba23
20 files changed
+448
-25
No files matched your search
@@ -18,6 +18,7 @@ pub mod cmd;
|
||||
|
||||
pub use cmd::analytic_surfaces;
|
||||
pub mod history;
|
||||
pub mod requirement;
|
||||
pub mod results;
|
||||
pub mod sample;
|
||||
pub mod schema;
|
||||
@@ -56,6 +57,9 @@ pub enum EngineError {
|
||||
Explicit(#[from] fieldcraft_explicit::Error),
|
||||
#[error(transparent)]
|
||||
Fluid(#[from] fieldcraft_lbm::Error),
|
||||
/// The session lacks what the command needs ([`requirement`]); nothing was done.
|
||||
#[error("{0}")]
|
||||
Unavailable(requirement::Unavailable),
|
||||
/// A command panicked; the model is unchanged.
|
||||
#[error("internal error in {command}: {message}")]
|
||||
Internal { command: String, message: String },
|
||||
@@ -113,6 +117,9 @@ pub struct CommandSpec {
|
||||
/// (the form is then the JSON text alone).
|
||||
pub form: Option<&'static [schema::Field]>,
|
||||
pub kind: CommandKind,
|
||||
/// What the session must hold before the command can run ([`requirement`]): checked by
|
||||
/// [`Session::execute`], and by the UI to grey the command with the missing pieces named.
|
||||
pub requires: &'static [requirement::Requirement],
|
||||
pub run: fn(&mut Session, &Value) -> Result<Value>,
|
||||
}
|
||||
|
||||
@@ -124,9 +131,9 @@ pub fn commands() -> &'static [CommandSpec] {
|
||||
impl CommandSpec {
|
||||
/// The command as JSON for listings (`fieldcraft-cli commands --json`, the control channel's
|
||||
/// `engine.commands`, MCP `list_commands`): id, label, the parameter doc and, when described,
|
||||
/// the parameters as JSON Schema.
|
||||
/// the parameters as JSON Schema, and what it requires.
|
||||
pub fn listing(&self) -> Value {
|
||||
let mut v = serde_json::json!({"id": self.id, "label": self.label, "params": self.params});
|
||||
let mut v = serde_json::json!({"id": self.id, "label": self.label, "params": self.params, "requires": self.requires});
|
||||
if let Some(form) = self.form {
|
||||
v["schema"] = schema::json_schema(form);
|
||||
}
|
||||
@@ -216,6 +223,7 @@ impl Session {
|
||||
pub fn execute(&mut self, id: &str, params: &Value) -> Result<Value> {
|
||||
// An id from hostile input is not echoed in full.
|
||||
let spec = find_command(id).ok_or_else(|| EngineError::UnknownCommand(id.chars().take(100).collect()))?;
|
||||
self.availability(spec).map_err(EngineError::Unavailable)?;
|
||||
// Session commands (open, save, undo, redo) change the session only in their last step,
|
||||
// after everything that can fail, so they need no copy of the model to restore.
|
||||
let before = (spec.kind == CommandKind::Model).then(|| (self.snapshot(), self.document()));
|
||||
|
||||
Reference in new issue
Block a user