Skip to content
This repository was archived by the owner on Jul 30, 2026. It is now read-only.
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
104 changes: 99 additions & 5 deletions crates/tokscale-core/src/adapters/file.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, PathBuf};

use rayon::prelude::*;

Expand Down Expand Up @@ -29,13 +29,15 @@ const GROK_RECORD_REJECTION_REVISION: u32 = GROK_TOTAL_ONLY_IMPUTATION_REVISION
const GROK_RELATED_METADATA_REVISION: u32 = GROK_RECORD_REJECTION_REVISION + 1;
const GEMINI_RECORD_REJECTION_REVISION: u32 = MODEL_ID_CANONICALIZATION_REVISION + 1;
const DROID_RECORD_REJECTION_REVISION: u32 = MODEL_ID_CANONICALIZATION_REVISION + 1;
const DROID_AGENT_ATTRIBUTION_REVISION: u32 = DROID_RECORD_REJECTION_REVISION + 1;
const GROK_RELATED_METADATA_SIBLINGS: &[&str] = &["summary.json", "events.jsonl"];

pub(crate) struct CachedFileAdapter {
client: ClientId,
parser_version: ParserVersion,
fingerprint_policy: FingerprintPolicy,
optional_related_inputs: bool,
dependency_path: Option<fn(&Path) -> Option<PathBuf>>,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
parse: fn(&Path) -> SessionParseResult<ScannedSource>,
}

Expand All @@ -51,6 +53,24 @@ impl CachedFileAdapter {
parser_version: ParserVersion::new(parser_id, revision),
fingerprint_policy: FingerprintPolicy::PlainFile,
optional_related_inputs: false,
dependency_path: None,
parse,
}
}

pub(crate) const fn new_with_dependency(
client: ClientId,
parser_id: ParserId,
revision: u32,
dependency_path: fn(&Path) -> Option<PathBuf>,
parse: fn(&Path) -> SessionParseResult<ScannedSource>,
) -> Self {
Self {
client,
parser_version: ParserVersion::new(parser_id, revision),
fingerprint_policy: FingerprintPolicy::PlainFile,
optional_related_inputs: false,
dependency_path: Some(dependency_path),
parse,
}
}
Expand All @@ -67,6 +87,7 @@ impl CachedFileAdapter {
parser_version: ParserVersion::new(parser_id, revision),
fingerprint_policy: FingerprintPolicy::PrimaryWithSiblings { sibling_names },
optional_related_inputs: true,
dependency_path: None,
parse,
}
}
Expand All @@ -87,7 +108,16 @@ impl LocalSourceAdapter for CachedFileAdapter {
self.fingerprint_policy.clone(),
)?
.into_iter()
.map(|unit| unit.with_parser_version(self.parser_version))
.map(|unit| {
let dependency_path = self
.dependency_path
.and_then(|dependency_path| dependency_path(&unit.path));
let unit = match dependency_path {
Some(dependency_path) => unit.with_dependency(dependency_path),
None => unit,
};
unit.with_parser_version(self.parser_version)
})
.collect())
}

Expand Down Expand Up @@ -218,10 +248,11 @@ pub(crate) static AMP_ADAPTER: CachedFileAdapter = CachedFileAdapter::new(
AMP_RECORD_REJECTION_REVISION,
sessions::amp::parse_amp_file,
);
pub(crate) static DROID_ADAPTER: CachedFileAdapter = CachedFileAdapter::new(
pub(crate) static DROID_ADAPTER: CachedFileAdapter = CachedFileAdapter::new_with_dependency(
ClientId::Droid,
ParserId::Droid,
DROID_RECORD_REJECTION_REVISION,
DROID_AGENT_ATTRIBUTION_REVISION,
sessions::droid::droid_agent_dependency_path,
sessions::droid::parse_droid_file,
);
pub(crate) static KIMI_ADAPTER: CachedFileAdapter = CachedFileAdapter::new(
Expand Down Expand Up @@ -563,6 +594,69 @@ not-json
assert_eq!(fold_ctx.health.failed_sources(), 0);
}

#[test]
fn droid_adapter_invalidates_cached_mission_worker_role_from_features() {
let home = tempfile::TempDir::new().unwrap();
let session_dir = home.path().join(".factory/sessions/project");
let settings_path = session_dir.join("mission-worker.settings.json");
let features_path = home
.path()
.join(".factory/missions/mission-root/features.json");
write_file(
&settings_path,
r#"{
"model": "custom:gpt-5.6-sol-xhigh",
"providerLock": "openai",
"providerLockTimestamp": "2026-07-15T08:55:13.871Z",
"tokenUsage": {"inputTokens": 10, "outputTokens": 5},
"tags": [
{"name": "exec"},
{"name": "mission-worker"},
{
"name": "mission-session",
"metadata": {"role": "worker", "missionId": "mission-root"}
}
]
}"#,
);
write_file(
&features_path,
r#"{"features":[{"id":"implementation","skillName":"backend-worker","workerSessionIds":["mission-worker"]}]}"#,
);
let settings = crate::scanner::ScannerSettings::default();
let ctx = scan_context(home.path(), &settings);
let unit = DROID_ADAPTER.discover_checked(&ctx).unwrap().pop().unwrap();
assert_eq!(
unit.fingerprint_policy,
FingerprintPolicy::PrimaryWithDependency {
dependency_path: features_path.clone()
}
);

let mut cache = message_cache::SourceMessageCache::default();
let worker_messages = fold_with_adapter(&DROID_ADAPTER, vec![unit], &mut cache);
assert_eq!(worker_messages.len(), 1);
assert_eq!(worker_messages[0].agent.as_deref(), Some("Droid Worker"));

write_file(
&features_path,
r#"{"features":[{"id":"scrutiny","skillName":"scrutiny-validator","workerSessionIds":["mission-worker"]}]}"#,
);
let changed_unit = DROID_ADAPTER.discover_checked(&ctx).unwrap().pop().unwrap();
let changed_unit = match DROID_ADAPTER.plan_cache_hit(changed_unit, &cache).unwrap() {
crate::adapters::CacheHitPlan::Miss(unit) => unit,
crate::adapters::CacheHitPlan::Hit(_) => {
panic!("changed Mission feature must invalidate the Droid source cache")
}
};
let validator_messages = fold_with_adapter(&DROID_ADAPTER, vec![changed_unit], &mut cache);
assert_eq!(validator_messages.len(), 1);
assert_eq!(
validator_messages[0].agent.as_deref(),
Some("Droid Validator")
);
}

#[test]
fn cached_file_adapters_use_their_actual_record_rejection_revisions() {
for (actual, parser_id, revision) in [
Expand All @@ -584,7 +678,7 @@ not-json
(
DROID_ADAPTER.parser_version,
ParserId::Droid,
DROID_RECORD_REJECTION_REVISION,
DROID_AGENT_ATTRIBUTION_REVISION,
),
(
KIMI_ADAPTER.parser_version,
Expand Down
Loading
Loading