feat: add builtin workflow resources
This commit is contained in:
@@ -72,6 +72,49 @@ impl fmt::Display for WorkflowResolveError {
|
||||
|
||||
impl std::error::Error for WorkflowResolveError {}
|
||||
|
||||
struct BuiltinKnowledgeResource {
|
||||
slug: &'static str,
|
||||
content: &'static str,
|
||||
}
|
||||
|
||||
const BUILTIN_KNOWLEDGE: &[BuiltinKnowledgeResource] = &[BuiltinKnowledgeResource {
|
||||
slug: "workflow-resource-boundary",
|
||||
content: include_str!("../../../../resources/knowledge/workflow-resource-boundary.md"),
|
||||
}];
|
||||
|
||||
fn builtin_knowledge(slug: &Slug) -> Option<&'static str> {
|
||||
BUILTIN_KNOWLEDGE
|
||||
.iter()
|
||||
.find(|resource| resource.slug == slug.as_str())
|
||||
.map(|resource| resource.content)
|
||||
}
|
||||
|
||||
fn read_required_knowledge(
|
||||
workflow: &Slug,
|
||||
layout: &WorkspaceLayout,
|
||||
req: &Slug,
|
||||
) -> Result<(String, &'static str), WorkflowResolveError> {
|
||||
let path = layout.knowledge_dir().join(format!("{req}.md"));
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(raw) => Ok((raw, "workspace")),
|
||||
Err(source) if source.kind() == std::io::ErrorKind::NotFound => {
|
||||
if let Some(raw) = builtin_knowledge(req) {
|
||||
Ok((raw.to_string(), "builtin"))
|
||||
} else {
|
||||
Err(WorkflowResolveError::KnowledgeNotFound {
|
||||
workflow: workflow.to_string(),
|
||||
slug: req.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(source) => Err(WorkflowResolveError::KnowledgeRead {
|
||||
workflow: workflow.to_string(),
|
||||
slug: req.to_string(),
|
||||
source,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_workflow_invocation(
|
||||
registry: &WorkflowRegistry,
|
||||
layout: &WorkspaceLayout,
|
||||
@@ -92,21 +135,7 @@ pub fn resolve_workflow_invocation(
|
||||
|
||||
let mut out = Vec::new();
|
||||
for req in &record.requires {
|
||||
let path = layout.knowledge_dir().join(format!("{req}.md"));
|
||||
let raw = std::fs::read_to_string(&path).map_err(|source| {
|
||||
if source.kind() == std::io::ErrorKind::NotFound {
|
||||
WorkflowResolveError::KnowledgeNotFound {
|
||||
workflow: slug.to_string(),
|
||||
slug: req.to_string(),
|
||||
}
|
||||
} else {
|
||||
WorkflowResolveError::KnowledgeRead {
|
||||
workflow: slug.to_string(),
|
||||
slug: req.to_string(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
})?;
|
||||
let (raw, knowledge_source) = read_required_knowledge(&slug, layout, req)?;
|
||||
let (_yaml, body) = split_frontmatter(&raw).map_err(|source| {
|
||||
WorkflowResolveError::KnowledgeFrontmatter {
|
||||
workflow: slug.to_string(),
|
||||
@@ -115,15 +144,17 @@ pub fn resolve_workflow_invocation(
|
||||
}
|
||||
})?;
|
||||
out.push(Item::system_message(format!(
|
||||
"[Workflow /{} requires Knowledge #{}]\n{}",
|
||||
"[Workflow /{} requires Knowledge #{} from {}]\n{}",
|
||||
slug,
|
||||
req,
|
||||
knowledge_source,
|
||||
body.trim_end()
|
||||
)));
|
||||
}
|
||||
out.push(Item::system_message(format!(
|
||||
"[Workflow /{}]\n{}",
|
||||
"[Workflow /{} from {}]\n{}",
|
||||
slug,
|
||||
record.source.label(),
|
||||
record.body.trim_end()
|
||||
)));
|
||||
Ok(out)
|
||||
@@ -165,10 +196,41 @@ mod tests {
|
||||
let second = format!("{:?}", items[1]);
|
||||
assert!(first.contains("Knowledge #policy"));
|
||||
assert!(first.contains("policy body"));
|
||||
assert!(second.contains("[Workflow /run-it]"));
|
||||
assert!(second.contains("[Workflow /run-it from workspace workflow]"));
|
||||
assert!(second.contains("workflow body"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_workflow_uses_builtin_required_knowledge_when_workspace_missing() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
let registry = workflow_crate::load_workflows(&layout).unwrap();
|
||||
let items =
|
||||
resolve_workflow_invocation(®istry, &layout, "ticket-intake-workflow").unwrap();
|
||||
let first = format!("{:?}", items[0]);
|
||||
let second = format!("{:?}", items[1]);
|
||||
assert!(first.contains("Knowledge #workflow-resource-boundary from builtin"));
|
||||
assert!(first.contains("Builtin workflow resources live under"));
|
||||
assert!(second.contains("[Workflow /ticket-intake-workflow from builtin workflow]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_knowledge_overrides_builtin_required_knowledge() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
write(
|
||||
&dir.path()
|
||||
.join(".yoi/knowledge/workflow-resource-boundary.md"),
|
||||
"---\ncreated_at: 2026-01-01T00:00:00Z\nupdated_at: 2026-01-01T00:00:00Z\nkind: policy\ndescription: p\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\nworkspace override knowledge\n",
|
||||
);
|
||||
let registry = workflow_crate::load_workflows(&layout).unwrap();
|
||||
let items =
|
||||
resolve_workflow_invocation(®istry, &layout, "ticket-intake-workflow").unwrap();
|
||||
let first = format!("{:?}", items[0]);
|
||||
assert!(first.contains("Knowledge #workflow-resource-boundary from workspace"));
|
||||
assert!(first.contains("workspace override knowledge"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_invocable_false_errors() {
|
||||
let (dir, layout, _registry) = setup();
|
||||
|
||||
Reference in New Issue
Block a user