feat: add explicit worker filesystem authority

This commit is contained in:
2026-07-11 07:30:17 +09:00
parent db3a7165f7
commit b50b94612a
13 changed files with 422 additions and 155 deletions
+62 -31
View File
@@ -274,7 +274,9 @@ impl WorkerController {
manifest_toml.clone(),
greeting,
));
shared_state.set_fs_view(crate::fs_view::WorkerFsView::new(fs_for_view));
if let Some(fs_for_view) = fs_for_view {
shared_state.set_fs_view(crate::fs_view::WorkerFsView::new(fs_for_view));
}
shared_state.set_workflows(
worker
.workflow_completions()
@@ -528,7 +530,10 @@ fn install_ticket_event_companion_notify_hook<C, St>(
return;
}
let Ok(ticket_config) = TicketConfig::load_workspace(worker.cwd()) else {
let Some(local) = worker.local_working_directory() else {
return;
};
let Ok(ticket_config) = TicketConfig::load_workspace(&local.cwd) else {
return;
};
let backend_root = ticket_config.backend_root().to_path_buf();
@@ -540,7 +545,7 @@ fn install_ticket_event_companion_notify_hook<C, St>(
worker.worker_metadata_store(),
worker.manifest().worker.name.clone(),
runtime_base,
worker.cwd().to_path_buf(),
Some(local.cwd.clone()),
spawned_registry,
);
match discovery.ensure_existing_peer(&companion_worker_name) {
@@ -589,7 +594,7 @@ async fn register_worker_tools<C, St>(
spawner_socket: PathBuf,
runtime_base: PathBuf,
spawned_registry: Arc<SpawnedWorkerRegistry>,
) -> std::io::Result<tools::ScopedFs>
) -> std::io::Result<Option<tools::ScopedFs>>
where
C: LlmClient + Clone + 'static,
St: Store + WorkerMetadataStore + Clone + 'static,
@@ -597,7 +602,7 @@ where
// Worker-immutable snapshots taken before the mutable worker borrow
// below so the worker borrow doesn't conflict with reads on `worker`.
let scope_handle = worker.scope().clone();
let cwd = worker.cwd().to_path_buf();
let local_filesystem = worker.local_working_directory().cloned();
let workspace_root = worker.workspace_root().to_path_buf();
let task_feature = worker.task_feature();
let session_id_for_usage = worker.segment_id().to_string();
@@ -611,24 +616,24 @@ where
let worker_metadata_store = worker.store().clone();
let self_parent_socket = worker.callback_socket().cloned();
// The Worker's SharedScope (already augmented with the bash-output
// Read rule by the caller) is the single source of truth — every
// ScopedFs (builtin tools, fs_view, compact worker) reads from it,
// and any future scope mutation (SpawnWorker-style revoke, future
// GrantScope) propagates through it.
let fs = tools::ScopedFs::with_shared_scope(scope_handle.clone(), cwd.clone());
let tracker = tools::Tracker::new();
// Same ScopedFs also powers the IPC `ListCompletions` query — keep
// a clone for the FS view we attach below, since the tools consume
// `fs` itself.
let fs_for_view = fs.clone();
worker
.engine_mut()
.register_tools(tools::core_builtin_tools(
fs,
tracker.clone(),
bash_output_dir,
));
// The Worker's SharedScope is the single source of truth for every
// ScopedFs when local filesystem authority exists. No-workdir Workers
// deliberately skip constructing/registering filesystem and Bash tools.
let (fs_for_view, tracker) = if let Some(local) = local_filesystem.as_ref() {
let fs = tools::ScopedFs::with_shared_scope(scope_handle.clone(), local.cwd.clone());
let tracker = tools::Tracker::new();
let fs_for_view = fs.clone();
worker
.engine_mut()
.register_tools(tools::core_builtin_tools(
fs,
tracker.clone(),
bash_output_dir,
));
(Some(fs_for_view), Some(tracker))
} else {
(None, None)
};
if feature_config.web.enabled {
worker
.engine_mut()
@@ -649,11 +654,20 @@ where
}
};
// Ticket tools are typed operations over the currently checked-out work
// tree. Use the Worker cwd rather than the runtime workspace root so a
// dedicated Orchestrator worktree gets its own `.yoi/tickets` backend.
// tree. They require explicit local filesystem authority; workspace_root
// is context only and must not be used as a cwd fallback.
let ticket_cwd = local_filesystem
.as_ref()
.map(|local| &local.cwd)
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"ticket tools require local Worker filesystem authority",
)
})?;
feature_registry.add_module(
crate::feature::builtin::ticket::ticket_tools_feature_with_options(
&cwd,
ticket_cwd,
feature_config.ticket.enabled.then_some(ticket_access),
feature_config.ticket_orchestration.enabled,
),
@@ -709,12 +723,21 @@ where
"[feature.workers].enabled = true requires non-empty [[delegation_scope.allow]]",
));
}
let spawner_cwd = local_filesystem
.as_ref()
.map(|local| local.cwd.clone())
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"worker spawn tools require local Worker filesystem authority",
)
})?;
worker.register_tool(spawn_worker_tool(
spawner_name.clone(),
spawner_socket,
runtime_base.clone(),
workspace_root.clone(),
cwd.clone(),
spawner_cwd.clone(),
spawned_registry.clone(),
self_parent_socket,
spawner_manifest,
@@ -728,7 +751,7 @@ where
worker_metadata_store,
spawner_name,
runtime_base,
cwd,
Some(spawner_cwd),
spawned_registry,
);
worker.register_tool(list_workers_tool(discovery.clone()));
@@ -737,7 +760,9 @@ where
}
}
let _feature_install_report = worker.install_features(feature_registry);
worker.attach_tracker(tracker);
if let Some(tracker) = tracker {
worker.attach_tracker(tracker);
}
Ok(fs_for_view)
}
@@ -774,11 +799,14 @@ async fn controller_loop<C, St>(
.parent()
.map(PathBuf::from)
.unwrap_or_else(|| runtime_dir.path().to_path_buf());
let discovery_cwd = worker
.local_working_directory()
.map(|local| local.cwd.clone());
let discovery = WorkerDiscovery::new(
worker.store().clone(),
spawner_name.clone(),
discovery_runtime_base,
worker.cwd().to_path_buf(),
discovery_cwd,
spawned_registry.clone(),
);
let mut pending: Option<PendingRun> = None;
@@ -1445,7 +1473,10 @@ where
.collect();
protocol::Greeting {
worker_name: manifest.worker.name.clone(),
cwd: worker.cwd().display().to_string(),
cwd: worker
.local_working_directory()
.map(|local| local.cwd.display().to_string())
.unwrap_or_default(),
provider: provider_name,
model: model_id,
scope_summary: worker.scope_snapshot().summary(),