Add configurable GOOSE_DOCS_ROOT for air-gapped docs access (#10294)
This commit is contained in:
+1
-1
@@ -124,7 +124,7 @@ Manage agent sessions: list, view, start, send messages, and interrupt agents.
|
||||
|
||||
|
||||
You have these skills at your disposal, when it is clear they can help you solve a problem or you are asked to use them:
|
||||
• goose-doc-guide - Reference goose documentation to create, configure, or explain goose-specific features like recipes, extensions, sessions, and providers. You MUST fetch relevant goose docs before answering. You MUST NOT rely on training data or assumptions for any goose-specific fields, values, names, syntax, or commands.
|
||||
• goose-doc-guide - Reference goose documentation to create, configure, or explain goose-specific features like recipes, extensions, sessions, and providers. You MUST read the relevant goose docs before answering. You MUST NOT rely on training data or assumptions for any goose-specific fields, values, names, syntax, or commands.
|
||||
## summarize
|
||||
|
||||
|
||||
|
||||
@@ -1162,6 +1162,14 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_goose_docs_root(&self) -> Result<Option<String>, ConfigError> {
|
||||
match self.get_param::<String>("GOOSE_DOCS_ROOT") {
|
||||
Ok(root) => Ok(Some(root.trim().to_string()).filter(|root| !root.is_empty())),
|
||||
Err(ConfigError::NotFound(_)) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_goose_thinking_effort(&self) -> Option<ThinkingEffort> {
|
||||
self.get_param::<String>("GOOSE_THINKING_EFFORT")
|
||||
.ok()
|
||||
@@ -2563,6 +2571,48 @@ extensions:
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_goose_docs_root_reads_config_file() {
|
||||
let _guard = env_lock::lock_env([("GOOSE_DOCS_ROOT", None::<&str>)]);
|
||||
let config = new_test_config();
|
||||
config
|
||||
.set_param("GOOSE_DOCS_ROOT", "/tmp/goose-docs")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config.get_goose_docs_root().unwrap(),
|
||||
Some("/tmp/goose-docs".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_goose_docs_root_reads_env_value() {
|
||||
let _guard = env_lock::lock_env([("GOOSE_DOCS_ROOT", Some("/tmp/env-docs"))]);
|
||||
let config = new_test_config();
|
||||
|
||||
assert_eq!(
|
||||
config.get_goose_docs_root().unwrap(),
|
||||
Some("/tmp/env-docs".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_goose_docs_root_returns_none_when_unset() {
|
||||
let _guard = env_lock::lock_env([("GOOSE_DOCS_ROOT", None::<&str>)]);
|
||||
let config = new_test_config();
|
||||
|
||||
assert_eq!(config.get_goose_docs_root().unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_goose_docs_root_ignores_blank_value() {
|
||||
let _guard = env_lock::lock_env([("GOOSE_DOCS_ROOT", None::<&str>)]);
|
||||
let config = new_test_config();
|
||||
config.set_param("GOOSE_DOCS_ROOT", " ").unwrap();
|
||||
|
||||
assert_eq!(config.get_goose_docs_root().unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_goose_thinking_effort_reads_env() {
|
||||
let _guard = env_lock::lock_env([
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: goose-doc-guide
|
||||
description: Reference goose documentation to create, configure, or explain goose-specific features like recipes, extensions, sessions, and providers. You MUST fetch relevant goose docs before answering. You MUST NOT rely on training data or assumptions for any goose-specific fields, values, names, syntax, or commands.
|
||||
description: Reference goose documentation to create, configure, or explain goose-specific features like recipes, extensions, sessions, and providers. You MUST read the relevant goose docs before answering. You MUST NOT rely on training data or assumptions for any goose-specific fields, values, names, syntax, or commands.
|
||||
---
|
||||
|
||||
Use this skill when working with **goose-specific features**:
|
||||
@@ -13,22 +13,28 @@ Do NOT use this skill for:
|
||||
- General coding tasks unrelated to goose
|
||||
- Running existing recipes (just run them directly)
|
||||
|
||||
The docs root for this session is `{{GOOSE_DOCS_ROOT}}`. It may be a local
|
||||
filesystem path or an HTTP(S) URL. When it is a local path read files with the
|
||||
shell/file tools; when it is not set or is an HTTP(S) URL fetch them from the
|
||||
canonical location which is https://goose-docs.ai. Everything below refers to
|
||||
this docs root as `<docs-root>`.
|
||||
|
||||
## Steps (COMPLETE ALL BEFORE RESPONDING)
|
||||
1. **Fetch official docs**
|
||||
- Fetch the doc map from `https://goose-docs.ai/goose-docs-map.md`
|
||||
1. **Read official docs**
|
||||
- Read the doc map from `<docs-root>/goose-docs-map.md`
|
||||
- Search the doc map for pages relevant to the user's topic and get the paths for these pages
|
||||
- Use the EXACT paths from the doc map. For example:
|
||||
- If doc map shows: `docs/guides/sessions/session-management.md`
|
||||
- Fetch: `https://goose-docs.ai/docs/guides/sessions/session-management.md`
|
||||
- Read: `<docs-root>/docs/guides/sessions/session-management.md`
|
||||
- Do NOT modify or guess paths.
|
||||
- **ONLY fetch paths that are explicitly listed in the doc map - do not guess or infer URLs**
|
||||
- Make multiple fetch calls in parallel and save to temp files
|
||||
- Use the temp files for subsequent searches instead of re-fetching
|
||||
- **ONLY read paths that are explicitly listed in the doc map - do not guess or infer paths**
|
||||
- Read multiple docs in parallel and save to temp files
|
||||
- Use the temp files for subsequent searches instead of re-reading
|
||||
|
||||
2. **Create/modify content**
|
||||
- For goose configuration files:
|
||||
- Consult schema/field reference documentation first
|
||||
- **Search the fetched docs to extract the complete schema for each element you plan to use**
|
||||
- **Search the docs to extract the complete schema for each element you plan to use**
|
||||
- Extract example snippets to understand usage patterns
|
||||
- Create your configuration based on reference specs, following example patterns
|
||||
- **⚠️ STOP: Before showing the user, verify output content MUST match the schema and reference in the goose official documentation:**
|
||||
@@ -43,14 +49,15 @@ Do NOT use this skill for:
|
||||
- [ ] You MUST NOT rely on training data or assumptions for any goose-specific fields, values, names, syntax, or commands.
|
||||
- [ ] **Did you include "How to Use", CLI commands, or usage instructions?**
|
||||
- If YES and user didn't ask for it → **REMOVE IT NOW**
|
||||
- If YES and user asked for it → verify exact commands from fetched docs before including
|
||||
- If YES and user asked for it → verify exact commands from the docs before including
|
||||
- [ ] List all goose-specific items in your answer (commands, fields, syntax, values, how to use, explanations, etc.)
|
||||
- [ ] For each item, verify it is correct according to the fetched docs. If not found, either fetch the relevant docs NOW and verify, or remove it (if user asked for it, state "I could not find documentation for [X]").
|
||||
- [ ] For each item, verify it is correct according to the docs. If not found, either read the relevant docs NOW and verify, or remove it (if user asked for it, state "I could not find documentation for [X]").
|
||||
|
||||
4. **Provide your answer and include a "Verification Completed" section**
|
||||
- For EACH goose-specific item in your response, cite the specific doc file where you verified it
|
||||
|
||||
5. **List documentation links**
|
||||
- Only include docs actually used
|
||||
- Always link to the canonical site `https://goose-docs.ai/`, even if you read the docs from a local path. Never expose local filesystem paths.
|
||||
- Remove `.md` suffix from URLs
|
||||
- Example: If you fetched `https://goose-docs.ai/docs/guides/sessions/session-management.md`, list it as `https://goose-docs.ai/docs/guides/sessions/session-management`
|
||||
- Example: If you read `docs/guides/sessions/session-management.md`, list it as `https://goose-docs.ai/docs/guides/sessions/session-management`
|
||||
|
||||
@@ -8,7 +8,7 @@ pub mod client;
|
||||
|
||||
pub use client::{SkillsClient, EXTENSION_NAME};
|
||||
|
||||
use crate::config::paths::Paths;
|
||||
use crate::config::{paths::Paths, Config};
|
||||
use crate::plugins::installed_plugin_skill_dirs;
|
||||
use crate::sources::parse_frontmatter;
|
||||
use agent_client_protocol::Error;
|
||||
@@ -99,7 +99,27 @@ pub(crate) fn validate_skill_name(name: &str) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn loaded_skill_context(skill: &SourceEntry, content: &str) -> String {
|
||||
const DEFAULT_GOOSE_DOCS_ROOT: &str = "https://goose-docs.ai";
|
||||
const GOOSE_DOCS_ROOT_PLACEHOLDER: &str = "{{GOOSE_DOCS_ROOT}}";
|
||||
|
||||
/// Substitute the `{{GOOSE_DOCS_ROOT}}` placeholder in the builtin
|
||||
/// `goose-doc-guide` skill with the resolved docs root. Resolution is
|
||||
/// deterministic: the configured `GOOSE_DOCS_ROOT` if set, otherwise the
|
||||
/// canonical online docs root.
|
||||
fn resolve_docs_root_placeholder(skill: &SourceEntry, content: &str, docs_root: &str) -> String {
|
||||
if skill.name != "goose-doc-guide" || skill.source_type != SourceType::BuiltinSkill {
|
||||
return content.to_string();
|
||||
}
|
||||
|
||||
content.replace(GOOSE_DOCS_ROOT_PLACEHOLDER, docs_root)
|
||||
}
|
||||
|
||||
fn loaded_skill_context(skill: &SourceEntry, content: &str) -> Result<String> {
|
||||
let docs_root = Config::global()
|
||||
.get_goose_docs_root()?
|
||||
.unwrap_or_else(|| DEFAULT_GOOSE_DOCS_ROOT.to_string());
|
||||
let content = resolve_docs_root_placeholder(skill, content, &docs_root);
|
||||
|
||||
let title = format!("{} ({})", skill.name, skill.source_type);
|
||||
let mut output = format!(
|
||||
"# Loaded Skill: {title}\n\n{}\n\n## Content\n\n{}\n",
|
||||
@@ -128,7 +148,7 @@ fn loaded_skill_context(skill: &SourceEntry, content: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn loaded_skill_context_with_args(skill: &SourceEntry, args: Option<&str>) -> Result<String> {
|
||||
@@ -138,7 +158,7 @@ pub fn loaded_skill_context_with_args(skill: &SourceEntry, args: Option<&str>) -
|
||||
skill.content.clone()
|
||||
};
|
||||
|
||||
Ok(loaded_skill_context(skill, &content))
|
||||
loaded_skill_context(skill, &content)
|
||||
}
|
||||
|
||||
pub fn skill_argument_hint(skill: &SourceEntry) -> Option<String> {
|
||||
@@ -518,6 +538,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn builtin_goose_doc_guide_skill() -> SourceEntry {
|
||||
SourceEntry {
|
||||
source_type: SourceType::BuiltinSkill,
|
||||
name: "goose-doc-guide".to_string(),
|
||||
description: "Test docs skill".to_string(),
|
||||
content: "Docs root: {{GOOSE_DOCS_ROOT}}.".to_string(),
|
||||
path: "builtin://skills/goose-doc-guide".to_string(),
|
||||
global: true,
|
||||
writable: true,
|
||||
supporting_files: Vec::new(),
|
||||
properties: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loaded_skill_context_with_args_replaces_arguments_placeholder_with_raw_args() {
|
||||
let skill = skill_with_content("Review $ARGUMENTS carefully.");
|
||||
@@ -553,4 +587,24 @@ mod tests {
|
||||
assert!(rendered.contains(&resolved_path));
|
||||
assert!(rendered.contains("load_skill(name: \"test-skill/scripts/my-tool.exe\")"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_docs_root_placeholder_substitutes_builtin_goose_doc_guide_root() {
|
||||
let skill = builtin_goose_doc_guide_skill();
|
||||
|
||||
let rendered =
|
||||
resolve_docs_root_placeholder(&skill, &skill.content, "/tmp/goose docs/root");
|
||||
|
||||
assert_eq!(rendered, "Docs root: /tmp/goose docs/root.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_docs_root_placeholder_ignores_non_builtin_goose_doc_guide_skills() {
|
||||
let mut skill = builtin_goose_doc_guide_skill();
|
||||
skill.source_type = SourceType::Skill;
|
||||
|
||||
let rendered = resolve_docs_root_placeholder(&skill, &skill.content, "/tmp/goose-docs");
|
||||
|
||||
assert_eq!(rendered, skill.content);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user