Enable resource reads for explicit executor skills (#35198)

## Why

Executor skills that disallow implicit invocation are omitted from `skills.list`, so an explicitly selected skill did not provide the authority and package identifiers needed to read its referenced resources.

## What changed

- Add `resource_access` metadata to injected instructions for explicit-only executor skills.
- Teach the `skills.read` description to accept that metadata as well as values returned by `skills.list`.
- Keep discoverable executor skill instructions unchanged.

## Testing

Add an app-server integration scenario that selects an explicit-only executor skill, confirms it remains absent from `skills.list`, and reads a referenced file through `skills.read`.

GitOrigin-RevId: 97d3b93d1618c013363788452303c8a31e32cc56
This commit is contained in:
jif authored and copyberry committed 2026-07-24 14:48:04 +00:00
1 parent 5f6a2c3adb
commit fe8500c0a0
4 files changed
+166 -66

No files matched your search

+8
View File
@@ -36,6 +36,7 @@ use crate::catalog::SkillCatalogEntry;
use crate::catalog::SkillReadResult;
use crate::catalog::SkillSourceKind;
use crate::fragments::AvailableSkillsInstructions;
use crate::fragments::ExecutorSkillResourceAccess;
use crate::fragments::SkillInstructions;
use crate::provider::HostSkillProvider;
use crate::provider::SkillListQuery;
@@ -464,6 +465,13 @@ where
)
.0,
contents,
executor_resource_access: (!entry.prompt_visible
&& entry.authority.kind == SkillSourceKind::Executor)
.then(|| ExecutorSkillResourceAccess {
authority_id: entry.authority.id.clone(),
package: entry.id.0.clone(),
main_resource: entry.main_prompt.as_str().to_string(),
}),
};
fragments.push(Box::new(fragment));
main_prompts_injected = true;
+24 -1
View File
@@ -77,6 +77,14 @@ pub(crate) struct SkillInstructions {
pub(crate) name: String,
pub(crate) path: String,
pub(crate) contents: String,
pub(crate) executor_resource_access: Option<ExecutorSkillResourceAccess>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ExecutorSkillResourceAccess {
pub(crate) authority_id: String,
pub(crate) package: String,
pub(crate) main_resource: String,
}
impl ContextualUserFragment for SkillInstructions {
@@ -96,6 +104,21 @@ impl ContextualUserFragment for SkillInstructions {
let name = &self.name;
let path = &self.path;
let contents = &self.contents;
format!("\n<name>{name}</name>\n<path>{path}</path>\n{contents}\n")
let resource_access = self
.executor_resource_access
.as_ref()
.map(|access| {
let metadata = serde_json::json!({
"authority": {
"kind": "executor",
"id": access.authority_id,
},
"package": access.package,
"main_resource": access.main_resource,
});
format!("\n<resource_access>{metadata}</resource_access>")
})
.unwrap_or_default();
format!("\n<name>{name}</name>\n<path>{path}</path>{resource_access}\n{contents}\n")
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ impl ToolExecutor<ToolCall> for ReadTool {
fn spec(&self) -> ToolSpec {
skill_function_tool::<ReadArgs, ReadResponse>(
TOOL_NAME,
"Read one page from a skill resource. Pass the exact authority and package from skills.list, plus its main_resource or a referenced resource beneath that package. Pass next_cursor back as cursor to continue.",
"Read one page from a skill resource. Pass the exact authority and package from skills.list or an explicitly selected skill's resource_access metadata, plus its main_resource or a referenced resource beneath that package. Pass next_cursor back as cursor to continue.",
)
}