fix: détection externe — champ detect (sonde PATH explicite) pour les lanceurs interpréteurs
lazycodex (run: npx lazycodex-ai) et nanobot (run: python -m nanobot) étaient déclarés « external » dès que npx/python étaient sur le PATH : la sonde utilisait le premier mot de run au lieu du vrai binaire. - AgentDef.detect : sonde PATH explicite, repli sur first_token sinon - probe.rs : cache invalidé quand les tokens du catalogue changent (token_hash), plus besoin d'attendre un changement de PATH - config.yaml : detect: lazycodex-ai / detect: nanobot - tests : detect_token_prefers_explicit_binary + 432 tests verts - v1.0.3, man pages régénérées
This commit is contained in:
+33
-5
@@ -79,6 +79,11 @@ struct ProbeCache {
|
||||
/// FNV-1a hash of the PATH string the token index was built from.
|
||||
#[serde(default)]
|
||||
path_hash: u64,
|
||||
/// FNV-1a hash of the catalog's detect tokens. When the catalog changes
|
||||
/// which binaries it probes (new agent, `detect` added), the token
|
||||
/// index must be rebuilt even if the PATH is unchanged.
|
||||
#[serde(default)]
|
||||
token_hash: u64,
|
||||
/// Catalog run-command first token -> resolved path (null = not found).
|
||||
#[serde(default)]
|
||||
tokens: BTreeMap<String, Option<String>>,
|
||||
@@ -98,6 +103,26 @@ fn path_hash() -> u64 {
|
||||
h
|
||||
}
|
||||
|
||||
/// Hash of the sorted catalog detect tokens: cache invalidation for catalog
|
||||
/// changes (new agent, added `detect` field) without re-scanning the PATH.
|
||||
fn catalog_token_hash(app: &App) -> u64 {
|
||||
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
let mut tokens: Vec<String> = app
|
||||
.catalog
|
||||
.agents()
|
||||
.iter()
|
||||
.filter_map(|a| a.detect_token())
|
||||
.collect();
|
||||
tokens.sort();
|
||||
for t in tokens {
|
||||
for b in t.bytes() {
|
||||
h ^= b as u64;
|
||||
h = h.wrapping_mul(0x0000_0100_0000_01b3);
|
||||
}
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
/// Build an index of every executable on the PATH in a single pass.
|
||||
/// Much cheaper than one which() call per agent when the catalog is large.
|
||||
fn build_path_index() -> BTreeMap<String, PathBuf> {
|
||||
@@ -163,13 +188,15 @@ pub fn detect_externals(app: &App) -> ExternalInfo {
|
||||
.unwrap_or_default();
|
||||
let mut dirty = false;
|
||||
|
||||
// Resolve the run-command tokens of the catalog (once per PATH change).
|
||||
// Resolve the detect tokens of the catalog (once per PATH change or
|
||||
// catalog change).
|
||||
let current_hash = path_hash();
|
||||
if cache.path_hash != current_hash {
|
||||
let current_tokens = catalog_token_hash(app);
|
||||
if cache.path_hash != current_hash || cache.token_hash != current_tokens {
|
||||
let index = build_path_index();
|
||||
cache.tokens.clear();
|
||||
for agent in app.catalog.agents() {
|
||||
let Some(token) = agent.first_token() else {
|
||||
let Some(token) = agent.detect_token() else {
|
||||
continue;
|
||||
};
|
||||
if !cache.tokens.contains_key(&token) {
|
||||
@@ -180,6 +207,7 @@ pub fn detect_externals(app: &App) -> ExternalInfo {
|
||||
}
|
||||
}
|
||||
cache.path_hash = current_hash;
|
||||
cache.token_hash = current_tokens;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@@ -195,7 +223,7 @@ pub fn detect_externals(app: &App) -> ExternalInfo {
|
||||
let index = build_path_index();
|
||||
cache.tokens.clear();
|
||||
for agent in app.catalog.agents() {
|
||||
let Some(token) = agent.first_token() else {
|
||||
let Some(token) = agent.detect_token() else {
|
||||
continue;
|
||||
};
|
||||
if !cache.tokens.contains_key(&token) {
|
||||
@@ -211,7 +239,7 @@ pub fn detect_externals(app: &App) -> ExternalInfo {
|
||||
if agent.hidden || !agent.installable || managed.contains_key(&agent.name) {
|
||||
continue;
|
||||
}
|
||||
let Some(token) = agent.first_token() else {
|
||||
let Some(token) = agent.detect_token() else {
|
||||
continue;
|
||||
};
|
||||
if let Some(Some(path)) = cache.tokens.get(&token) {
|
||||
|
||||
Reference in New Issue
Block a user