worker: preserve prompt overrides in internal children

This commit is contained in:
2026-08-07 02:15:00 +09:00
parent ee75272917
commit e6703ed18a
2 changed files with 33 additions and 3 deletions
+6 -1
View File
@@ -251,6 +251,7 @@ struct PackFile {
/// `$yoi` / `$user` / `$workspace`. /// `$yoi` / `$user` / `$workspace`.
pub struct PromptCatalog { pub struct PromptCatalog {
env: Environment<'static>, env: Environment<'static>,
loader: PromptLoader,
} }
impl std::fmt::Debug for PromptCatalog { impl std::fmt::Debug for PromptCatalog {
@@ -260,6 +261,10 @@ impl std::fmt::Debug for PromptCatalog {
} }
impl PromptCatalog { impl PromptCatalog {
pub(crate) fn loader(&self) -> PromptLoader {
self.loader.clone()
}
/// Builtin-only catalog. All `{% include %}` references must resolve /// Builtin-only catalog. All `{% include %}` references must resolve
/// through `$yoi` (user/workspace prefixes are unavailable). /// through `$yoi` (user/workspace prefixes are unavailable).
pub fn builtins_only() -> Result<Arc<Self>, CatalogError> { pub fn builtins_only() -> Result<Arc<Self>, CatalogError> {
@@ -483,7 +488,7 @@ fn build_catalog(
})?; })?;
} }
Ok(PromptCatalog { env }) Ok(PromptCatalog { env, loader })
} }
#[cfg(test)] #[cfg(test)]
+27 -2
View File
@@ -226,6 +226,7 @@ pub struct SubWorkerSpawnTool {
/// child config from reusable fields here, and selected profiles are /// child config from reusable fields here, and selected profiles are
/// merged into the same internal handoff shape before launch. /// merged into the same internal handoff shape before launch.
spawner_manifest: WorkerManifest, spawner_manifest: WorkerManifest,
prompt_loader: PromptLoader,
/// Compact selector list shared by tool description and diagnostics. /// Compact selector list shared by tool description and diagnostics.
available_profiles: AvailableProfiles, available_profiles: AvailableProfiles,
/// Spawner's runtime scope. After a successful spawn, the /// Spawner's runtime scope. After a successful spawn, the
@@ -259,6 +260,7 @@ impl SubWorkerSpawnTool {
spawner_cwd: PathBuf, spawner_cwd: PathBuf,
registry: Arc<SpawnedWorkerRegistry>, registry: Arc<SpawnedWorkerRegistry>,
spawner_manifest: WorkerManifest, spawner_manifest: WorkerManifest,
prompt_loader: PromptLoader,
available_profiles: AvailableProfiles, available_profiles: AvailableProfiles,
spawner_scope: SharedScope, spawner_scope: SharedScope,
delegation_scope: DelegationScope, delegation_scope: DelegationScope,
@@ -272,6 +274,7 @@ impl SubWorkerSpawnTool {
spawner_cwd, spawner_cwd,
registry, registry,
spawner_manifest, spawner_manifest,
prompt_loader,
available_profiles, available_profiles,
spawner_scope, spawner_scope,
delegation_scope, delegation_scope,
@@ -340,7 +343,7 @@ impl Tool for SubWorkerSpawnTool {
let mut child = Worker::<Box<dyn llm_engine::llm_client::LlmClient>, EphemeralSessionStore>::from_internal_manifest_with_context( let mut child = Worker::<Box<dyn llm_engine::llm_client::LlmClient>, EphemeralSessionStore>::from_internal_manifest_with_context(
child_manifest, child_manifest,
store.clone(), store.clone(),
PromptLoader::builtins_only(), self.prompt_loader.clone(),
self.workspace_context.clone(), self.workspace_context.clone(),
filesystem_authority, filesystem_authority,
self.internal_client_override self.internal_client_override
@@ -803,6 +806,7 @@ fn sub_worker_spawn_tool_impl(
spawner_cwd.clone(), spawner_cwd.clone(),
registry.clone(), registry.clone(),
spawner_manifest.clone(), spawner_manifest.clone(),
prompts.loader(),
available_profiles, available_profiles,
spawner_scope.clone(), spawner_scope.clone(),
DelegationScope::from_config(&spawner_manifest.delegation_scope) DelegationScope::from_config(&spawner_manifest.delegation_scope)
@@ -858,6 +862,15 @@ mod tests {
); );
let calls = Arc::new(AtomicUsize::new(0)); let calls = Arc::new(AtomicUsize::new(0));
let observed_parent_write_revoked = Arc::new(std::sync::atomic::AtomicBool::new(false)); let observed_parent_write_revoked = Arc::new(std::sync::atomic::AtomicBool::new(false));
let observed_instruction_override = Arc::new(std::sync::atomic::AtomicBool::new(false));
let workspace_prompts = runtime.path().join("workspace-prompts");
std::fs::create_dir_all(&workspace_prompts).unwrap();
std::fs::write(
workspace_prompts.join("custom-reviewer.md"),
"WORKSPACE REVIEWER OVERRIDE",
)
.unwrap();
let prompt_loader = PromptLoader::new(None, Some(workspace_prompts));
let parent_notifies = crate::ipc::notify_buffer::NotifyBuffer::new(); let parent_notifies = crate::ipc::notify_buffer::NotifyBuffer::new();
let tool = SubWorkerSpawnTool::new( let tool = SubWorkerSpawnTool::new(
"parent".into(), "parent".into(),
@@ -868,6 +881,7 @@ mod tests {
workspace_root.clone(), workspace_root.clone(),
registry.clone(), registry.clone(),
manifest.clone(), manifest.clone(),
prompt_loader,
AvailableProfiles::discover(&workspace_root), AvailableProfiles::discover(&workspace_root),
spawner_scope.clone(), spawner_scope.clone(),
DelegationScope::from_config(&manifest.delegation_scope).unwrap(), DelegationScope::from_config(&manifest.delegation_scope).unwrap(),
@@ -877,10 +891,12 @@ mod tests {
parent_scope: spawner_scope.clone(), parent_scope: spawner_scope.clone(),
delegated_path: workspace_root.clone(), delegated_path: workspace_root.clone(),
observed_parent_write_revoked: observed_parent_write_revoked.clone(), observed_parent_write_revoked: observed_parent_write_revoked.clone(),
observed_instruction_override: observed_instruction_override.clone(),
})); }));
let input = serde_json::json!({ let input = serde_json::json!({
"name": "reviewer-child", "name": "reviewer-child",
"profile": "builtin:reviewer", "profile": "builtin:reviewer",
"instruction": "$workspace/custom-reviewer",
"task": "review immutable commit", "task": "review immutable commit",
"scope": [{ "scope": [{
"target": workspace_root.clone(), "target": workspace_root.clone(),
@@ -909,6 +925,7 @@ mod tests {
); );
assert_eq!(calls.load(Ordering::SeqCst), 1); assert_eq!(calls.load(Ordering::SeqCst), 1);
assert!(observed_parent_write_revoked.load(Ordering::SeqCst)); assert!(observed_parent_write_revoked.load(Ordering::SeqCst));
assert!(observed_instruction_override.load(Ordering::SeqCst));
assert_eq!(parent_notifies.len(), 1); assert_eq!(parent_notifies.len(), 1);
assert!(!runtime.path().join("reviewer-child/sock").exists()); assert!(!runtime.path().join("reviewer-child/sock").exists());
@@ -1080,6 +1097,7 @@ mod tests {
parent_scope: SharedScope, parent_scope: SharedScope,
delegated_path: PathBuf, delegated_path: PathBuf,
observed_parent_write_revoked: Arc<std::sync::atomic::AtomicBool>, observed_parent_write_revoked: Arc<std::sync::atomic::AtomicBool>,
observed_instruction_override: Arc<std::sync::atomic::AtomicBool>,
} }
#[async_trait] #[async_trait]
@@ -1090,7 +1108,7 @@ mod tests {
async fn stream( async fn stream(
&self, &self,
_request: Request, request: Request,
) -> Result<Pin<Box<dyn Stream<Item = Result<LlmEvent, ClientError>> + Send>>, ClientError> ) -> Result<Pin<Box<dyn Stream<Item = Result<LlmEvent, ClientError>> + Send>>, ClientError>
{ {
self.calls.fetch_add(1, Ordering::SeqCst); self.calls.fetch_add(1, Ordering::SeqCst);
@@ -1101,6 +1119,13 @@ mod tests {
.is_writable(&self.delegated_path), .is_writable(&self.delegated_path),
Ordering::SeqCst, Ordering::SeqCst,
); );
self.observed_instruction_override.store(
request
.system_prompt
.as_deref()
.is_some_and(|prompt| prompt.contains("WORKSPACE REVIEWER OVERRIDE")),
Ordering::SeqCst,
);
Ok(Box::pin(futures::stream::iter(vec![ Ok(Box::pin(futures::stream::iter(vec![
Ok(LlmEvent::text_block_start(0)), Ok(LlmEvent::text_block_start(0)),
Ok(LlmEvent::text_delta(0, "reviewed")), Ok(LlmEvent::text_delta(0, "reviewed")),