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
87 changes: 78 additions & 9 deletions crates/aft/src/subc/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pub(super) fn is_subc_native_plumbing_tool(name: &str) -> bool {
)
}

pub(super) fn command_lane(command: &str) -> Lane {
pub(super) fn command_lane_explicit(command: &str) -> Option<Lane> {
match command {
"ping"
| "version"
Expand All @@ -108,12 +108,12 @@ pub(super) fn command_lane(command: &str) -> Lane {
| "glob"
| "grep"
| "git_conflicts"
| "ast_search" => Lane::PureRead,
| "ast_search" => Some(Lane::PureRead),

// Lazy reads mutate parser/terminal/url caches on a miss, but are still
// classified onto the reader pool; install races are handled at the
// individual cache sites.
"bash_status" | "outline" | "zoom" => Lane::PureRead,
"bash_status" | "outline" | "zoom" => Some(Lane::PureRead),

"status"
| "inspect"
Expand All @@ -122,10 +122,12 @@ pub(super) fn command_lane(command: &str) -> Lane {
| "lsp_hover"
| "lsp_goto_definition"
| "lsp_find_references"
| "lsp_prepare_rename" => Lane::SerialLspStatus,
| "lsp_prepare_rename" => Some(Lane::SerialLspStatus),

"semantic_search" | "search" | "callgraph" | "callers" | "impact" | "call_tree"
| "trace_to" | "trace_to_symbol" | "trace_data" | "inspect_tier2_run" => Lane::HeavyInit,
| "trace_to" | "trace_to_symbol" | "trace_data" | "inspect_tier2_run" => {
Some(Lane::HeavyInit)
}

"bash"
| "bash_abort_inflight"
Expand All @@ -141,30 +143,40 @@ pub(super) fn command_lane(command: &str) -> Lane {
| "checkpoint"
| "restore_checkpoint"
| "write"
| "apply_patch"
| "delete_file"
| "delete"
| "move_file"
| "move"
| "edit"
| "edit_symbol"
| "edit_match"
| "batch"
| "add_import"
| "import"
| "remove_import"
| "organize_imports"
| "configure"
| "refactor"
| "move_symbol"
| "extract_function"
| "inline_symbol"
| "ast_replace"
| "safety"
| "lsp_rename"
| "list_filters"
| "trust_filter_project"
| "untrust_filter_project"
| "snapshot" => Lane::Mutating,
| "snapshot" => Some(Lane::Mutating),

_ => Lane::Mutating,
_ => None,
}
}

pub(super) fn command_lane(command: &str) -> Lane {
command_lane_explicit(command).unwrap_or(Lane::Mutating)
}

static SUBC_TOOL_SCHEMAS: LazyLock<serde_json::Map<String, Value>> = LazyLock::new(|| {
serde_json::from_str(include_str!("../subc_tool_schemas.json"))
.unwrap_or_else(|e| panic!("subc_tool_schemas.json: {e}"))
Expand Down Expand Up @@ -270,9 +282,10 @@ pub(super) fn control_flags() -> Flags {
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use crate::subc_translate::supports_tool;
use std::collections::{HashMap, HashSet};

const CORE_TOOLS: [&str; 21] = [
const CORE_TOOLS: &[&str] = &[
"status",
"bash",
"read",
Expand All @@ -296,6 +309,10 @@ mod tests {
"safety",
];

/// Tools listed here deliberately skip translation; adding one is a reviewed
/// decision because it weakens the registration guard's translation check.
const TRANSLATION_EXEMPT: &[&str] = &[];

fn is_bare_placeholder_schema(schema: &Value) -> bool {
schema == &json!({ "type": "object" })
}
Expand Down Expand Up @@ -357,6 +374,58 @@ mod tests {
);
}

#[test]
fn embedded_subc_tools_are_registered_across_all_rust_surfaces() {
let schema_names: HashSet<&str> = SUBC_TOOL_SCHEMAS.keys().map(String::as_str).collect();
let core_names: HashSet<&str> = CORE_TOOLS.iter().copied().collect();
assert_eq!(
CORE_TOOLS.len(),
schema_names.len(),
"CORE_TOOLS count must match embedded schema key count"
);
assert_eq!(
core_names, schema_names,
"CORE_TOOLS must exactly match embedded schema keys"
);

let manifest = build_manifest();
let tools = match manifest.provides.first() {
Some(ProviderRole::ToolProvider { tools, .. }) => tools,
_ => panic!("expected ToolProvider"),
};
let manifest_names: HashSet<&str> = tools.iter().map(|tool| tool.name.as_str()).collect();

for name in schema_names {
assert!(
is_subc_agent_core_tool(name),
"tool {name:?} is missing from is_subc_agent_core_tool in crates/aft/src/subc/manifest.rs"
);
assert!(
manifest_names.contains(name),
"tool {name:?} is missing from build_manifest in crates/aft/src/subc/manifest.rs"
);
assert!(
command_lane_explicit(name).is_some(),
"tool {name:?} is missing an explicit command_lane arm in crates/aft/src/subc/manifest.rs"
);
if !TRANSLATION_EXEMPT.contains(&name) {
assert!(
supports_tool(name),
"tool {name:?} is missing from supports_tool in crates/aft/src/subc_translate.rs"
);
}
}

// BARE_TOOL_ORDER is TypeScript-only; the embedded schema map is its
// generated Rust-side artifact, so the manifest count is the Rust
// denominator check for this derived guard.
assert_eq!(
SUBC_TOOL_SCHEMAS.len(),
tools.len(),
"registration guard denominator must match manifest tool count"
);
}

#[test]
fn build_manifest_classifies_execution_mode_by_observable_effect() {
let manifest = build_manifest();
Expand Down
11 changes: 10 additions & 1 deletion crates/aft/tests/integration/subc_bridge_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6883,7 +6883,16 @@ async fn drive_module_hello_health_manifest_daemon(input: FakeDaemonInput) {
Some(subc_protocol::manifest::ProviderRole::ToolProvider { tools, .. }) => tools,
other => panic!("expected first provider role to be ToolProvider, got {other:?}"),
};
assert_eq!(tools.len(), 21, "expected 21 manifest tools");
let schema_count = serde_json::from_str::<serde_json::Map<String, Value>>(include_str!(
"../../src/subc_tool_schemas.json"
))
.expect("embedded subc tool schemas should be a JSON object")
.len();
assert_eq!(
tools.len(),
schema_count,
"manifest tool count must match embedded schema key count"
);
for tool in tools {
assert!(
tool.description
Expand Down
26 changes: 13 additions & 13 deletions docs/v0.49-agent-prefix-capture.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"artifact_id": "ART-V049-S5-AGENT-PREFIX-CAPTURE-001",
"artifact_version": "0.49.0",
"source_commit": "d842d702a6afa8395c810282591c062e17cff521",
"source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248",
"capture_scope": "complete plugin-owned production agent-prefix input for every checked host profile; host-owned base prompt text is outside the plugin boundary",
"captures": [
{
Expand All @@ -18,7 +18,7 @@
"exposed": false,
"source": "host did not expose a production cache key"
},
"source_commit": "d842d702a6afa8395c810282591c062e17cff521",
"source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248",
"prefix_input": {
"registered_tool_names": [
"aft_outline",
Expand Down Expand Up @@ -201,7 +201,7 @@
"exposed": false,
"source": "host did not expose a production cache key"
},
"source_commit": "d842d702a6afa8395c810282591c062e17cff521",
"source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248",
"prefix_input": {
"registered_tool_names": [
"aft_conflicts",
Expand Down Expand Up @@ -1061,7 +1061,7 @@
"exposed": false,
"source": "host did not expose a production cache key"
},
"source_commit": "d842d702a6afa8395c810282591c062e17cff521",
"source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248",
"prefix_input": {
"registered_tool_names": [
"aft_callgraph",
Expand Down Expand Up @@ -2081,16 +2081,16 @@
"profile_id": "REG-V049-PI-MIN",
"harness": "pi",
"host_version": {
"value": "0.84.0",
"method": "pi --version"
"value": null,
"method": "pi --version (not available in capture environment)"
},
"capture_method": "production registerPiToolSurface registration output plus before_agent_start workflow hint input",
"production_cache_key": {
"value": null,
"exposed": false,
"source": "host did not expose a production cache key"
},
"source_commit": "d842d702a6afa8395c810282591c062e17cff521",
"source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248",
"prefix_input": {
"registered_tool_names": [
"aft_outline",
Expand Down Expand Up @@ -2272,16 +2272,16 @@
"profile_id": "REG-V049-PI-REC",
"harness": "pi",
"host_version": {
"value": "0.84.0",
"method": "pi --version"
"value": null,
"method": "pi --version (not available in capture environment)"
},
"capture_method": "production registerPiToolSurface registration output plus before_agent_start workflow hint input",
"production_cache_key": {
"value": null,
"exposed": false,
"source": "host did not expose a production cache key"
},
"source_commit": "d842d702a6afa8395c810282591c062e17cff521",
"source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248",
"prefix_input": {
"registered_tool_names": [
"aft_conflicts",
Expand Down Expand Up @@ -3240,16 +3240,16 @@
"profile_id": "REG-V049-PI-ALL",
"harness": "pi",
"host_version": {
"value": "0.84.0",
"method": "pi --version"
"value": null,
"method": "pi --version (not available in capture environment)"
},
"capture_method": "production registerPiToolSurface registration output plus before_agent_start workflow hint input",
"production_cache_key": {
"value": null,
"exposed": false,
"source": "host did not expose a production cache key"
},
"source_commit": "d842d702a6afa8395c810282591c062e17cff521",
"source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248",
"prefix_input": {
"registered_tool_names": [
"aft_callgraph",
Expand Down
Loading
Loading