diff --git a/TODO.md b/TODO.md index c29c3278..caa3d567 100644 --- a/TODO.md +++ b/TODO.md @@ -7,6 +7,7 @@ - [ ] LLM モデルカタログ + マニフェスト ref 解決 → [tickets/llm-model-catalog.md](tickets/llm-model-catalog.md) - [ ] Pod オーケストレーション - [ ] 動的 Scope 変更 → [tickets/dynamic-scope.md](tickets/dynamic-scope.md) + - [ ] pod クレートのモジュール分割 → [tickets/pod-module-layout.md](tickets/pod-module-layout.md) - [ ] ネイティブ GUI クライアント MVP → [tickets/native-gui-mvp.md](tickets/native-gui-mvp.md) - [ ] TUI 拡充 - [ ] フルスクリーン化によるオーバーホール → [tickets/tui-fullscreen-overhaul.md](tickets/tui-fullscreen-overhaul.md) diff --git a/crates/pod/src/compact/mod.rs b/crates/pod/src/compact/mod.rs new file mode 100644 index 00000000..79316add --- /dev/null +++ b/crates/pod/src/compact/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod prune; +pub(crate) mod state; +pub(crate) mod token_counter; +pub(crate) mod usage_tracker; +pub(crate) mod worker; diff --git a/crates/pod/src/prune.rs b/crates/pod/src/compact/prune.rs similarity index 97% rename from crates/pod/src/prune.rs rename to crates/pod/src/compact/prune.rs index 480f806a..166a7a9c 100644 --- a/crates/pod/src/prune.rs +++ b/crates/pod/src/compact/prune.rs @@ -12,7 +12,7 @@ use llm_worker::prune::{PruneConfig, SavingsEstimator}; use session_store::Store; use crate::Pod; -use crate::token_counter::{EstimateSource, savings_for_prune_impl}; +use crate::compact::token_counter::{EstimateSource, savings_for_prune_impl}; impl Pod { /// Enable prune projection on the underlying Worker. diff --git a/crates/pod/src/compact_state.rs b/crates/pod/src/compact/state.rs similarity index 100% rename from crates/pod/src/compact_state.rs rename to crates/pod/src/compact/state.rs diff --git a/crates/pod/src/token_counter.rs b/crates/pod/src/compact/token_counter.rs similarity index 100% rename from crates/pod/src/token_counter.rs rename to crates/pod/src/compact/token_counter.rs diff --git a/crates/pod/src/usage_tracker.rs b/crates/pod/src/compact/usage_tracker.rs similarity index 100% rename from crates/pod/src/usage_tracker.rs rename to crates/pod/src/compact/usage_tracker.rs diff --git a/crates/pod/src/compact_worker.rs b/crates/pod/src/compact/worker.rs similarity index 100% rename from crates/pod/src/compact_worker.rs rename to crates/pod/src/compact/worker.rs diff --git a/crates/pod/src/controller.rs b/crates/pod/src/controller.rs index 5b07e91c..a57627a5 100644 --- a/crates/pod/src/controller.rs +++ b/crates/pod/src/controller.rs @@ -6,17 +6,17 @@ use llm_worker::llm_client::client::LlmClient; use session_store::Store; use tokio::sync::{broadcast, mpsc, oneshot}; -use crate::notification_buffer::NotificationBuffer; -use crate::notifier::Notifier; +use crate::ipc::notification_buffer::NotificationBuffer; +use crate::ipc::notifier::Notifier; use crate::pod::{Pod, PodError, PodRunResult}; -use crate::pod_comm_tools::{ +use crate::spawn::comm_tools::{ list_pods_tool, read_pod_output_tool, send_to_pod_tool, stop_pod_tool, }; -use crate::runtime_dir::RuntimeDir; +use crate::runtime::dir::RuntimeDir; use crate::shared_state::{PodSharedState, PodStatus}; -use crate::socket_server::SocketServer; -use crate::spawn_pod::spawn_pod_tool; -use crate::spawned_pod_registry::SpawnedPodRegistry; +use crate::ipc::server::SocketServer; +use crate::spawn::tool::spawn_pod_tool; +use crate::spawn::registry::SpawnedPodRegistry; use protocol::{ErrorCode, Event, Method, NotificationLevel, NotificationSource, RunResult, TurnResult}; // --------------------------------------------------------------------------- @@ -463,7 +463,7 @@ impl PodController { // (1) system side effects — idempotent and // tolerant of out-of-order delivery (e.g. // `TurnEnded` arriving after `ShutDown`). - crate::pod_events::apply_event_side_effects( + crate::ipc::event::apply_event_side_effects( &event, &spawned_registry, &spawner_name, @@ -474,7 +474,7 @@ impl PodController { // into the notification buffer; the next LLM // request will inject it as a system message // via `PodInterceptor::pre_llm_request`. - let text = crate::pod_events::render_event(&event); + let text = crate::ipc::event::render_event(&event); pod.push_notification(text); // Auto-kick a turn if the Pod is idle so the // notification is not stranded. Matches the @@ -529,7 +529,7 @@ impl PodController { // `connect_and_send` helper enforces a 5 s timeout so a // stuck parent cannot block process exit indefinitely. if let Some(parent) = self_parent_socket.as_ref() { - if let Err(e) = crate::pod_events::send_pod_event( + if let Err(e) = crate::ipc::event::send_pod_event( parent, protocol::PodEvent::ShutDown { pod_name: spawner_name.clone(), @@ -587,7 +587,7 @@ where }; let _ = event_tx.send(Event::RunEnd { result: run_result }); if matches!(run_result, RunResult::Finished) { - crate::pod_events::fire_and_forget( + crate::ipc::event::fire_and_forget( parent_socket.cloned(), protocol::PodEvent::TurnEnded { pod_name: self_name.to_string(), @@ -612,7 +612,7 @@ where code, message: message.clone(), }); - crate::pod_events::fire_and_forget( + crate::ipc::event::fire_and_forget( parent_socket.cloned(), protocol::PodEvent::Errored { pod_name: self_name.to_string(), @@ -657,14 +657,14 @@ where // notification buffer so the in-flight turn's // next `pre_llm_request` surfaces it. let self_parent_socket = parent_socket.cloned(); - crate::pod_events::apply_event_side_effects( + crate::ipc::event::apply_event_side_effects( &event, spawned_registry, self_name, &self_parent_socket, ) .await; - notification_buffer.push(crate::pod_events::render_event(&event)); + notification_buffer.push(crate::ipc::event::render_event(&event)); } None => { let _ = cancel_tx.try_send(()); diff --git a/crates/pod/src/factory.rs b/crates/pod/src/factory.rs index 302f2d69..bea73791 100644 --- a/crates/pod/src/factory.rs +++ b/crates/pod/src/factory.rs @@ -29,7 +29,7 @@ use std::path::{Path, PathBuf}; use manifest::{PodManifest, PodManifestConfig, ResolveError}; -use crate::prompt_loader::PromptLoader; +use crate::prompt::loader::PromptLoader; /// Errors raised while building a [`PodManifest`] from cascade layers. #[derive(Debug, thiserror::Error)] @@ -614,7 +614,7 @@ permission = "write" #[test] fn resolve_produces_loader_with_workspace_prompts_dir() { - use crate::system_prompt::{SystemPromptContext, SystemPromptTemplate}; + use crate::prompt::system::{SystemPromptContext, SystemPromptTemplate}; use manifest::{Permission, Scope, ScopeConfig, ScopeRule}; let tmp = TempDir::new().unwrap(); @@ -664,7 +664,7 @@ permission = "write" deny: Vec::new(), }; let scope = Scope::from_config(&scope_cfg).unwrap(); - let catalog = crate::prompts::PromptCatalog::builtins_only().unwrap(); + let catalog = crate::prompt::catalog::PromptCatalog::builtins_only().unwrap(); let ctx = SystemPromptContext { now: chrono::Utc::now(), cwd: &root, diff --git a/crates/pod/src/interrupt_and_run.rs b/crates/pod/src/interrupt_and_run.rs index e69bab3d..d6af09a5 100644 --- a/crates/pod/src/interrupt_and_run.rs +++ b/crates/pod/src/interrupt_and_run.rs @@ -15,7 +15,7 @@ use session_store::Store; use crate::pod::{Pod, PodError, PodRunResult}; #[cfg(test)] -use crate::prompts::PromptCatalog; +use crate::prompt::catalog::PromptCatalog; impl Pod { /// Close out the current (paused) turn and start a new one with `input`. diff --git a/crates/pod/src/pod_events.rs b/crates/pod/src/ipc/event.rs similarity index 97% rename from crates/pod/src/pod_events.rs rename to crates/pod/src/ipc/event.rs index 1c26bfd5..f18174a9 100644 --- a/crates/pod/src/pod_events.rs +++ b/crates/pod/src/ipc/event.rs @@ -26,10 +26,10 @@ use std::sync::Arc; use protocol::{Method, PodEvent, ScopeRule}; -use crate::pod_comm_tools::connect_and_send; -use crate::runtime_dir::SpawnedPodRecord; -use crate::scope_lock::{self, ScopeLockError}; -use crate::spawned_pod_registry::SpawnedPodRegistry; +use crate::spawn::comm_tools::connect_and_send; +use crate::runtime::dir::SpawnedPodRecord; +use crate::runtime::scope_lock::{self, ScopeLockError}; +use crate::spawn::registry::SpawnedPodRegistry; /// Connect to `socket`, send a single `Method::PodEvent(event)`, and /// return. Used by children to report up to their parent. diff --git a/crates/pod/src/pod_interceptor.rs b/crates/pod/src/ipc/interceptor.rs similarity index 98% rename from crates/pod/src/pod_interceptor.rs rename to crates/pod/src/ipc/interceptor.rs index 12104dbe..6c8de01b 100644 --- a/crates/pod/src/pod_interceptor.rs +++ b/crates/pod/src/ipc/interceptor.rs @@ -20,14 +20,14 @@ use llm_worker::tool::ToolOutput; use session_store::UsageRecord; use tracing::info; -use crate::compact_state::CompactState; +use crate::compact::state::CompactState; use crate::hook::{ AbortInfo, HookRegistry, PreRequestInfo, PromptSubmitInfo, ToolCallSummary, ToolResultSummary, TurnEndInfo, }; -use crate::notification_buffer::{NotificationBuffer, format_notification}; -use crate::prompts::PromptCatalog; -use crate::token_counter::total_tokens_impl; +use crate::ipc::notification_buffer::{NotificationBuffer, format_notification}; +use crate::prompt::catalog::PromptCatalog; +use crate::compact::token_counter::total_tokens_impl; use tracing::warn; /// Maximum number of bytes copied into `TurnEndInfo::final_text_preview`. diff --git a/crates/pod/src/ipc/mod.rs b/crates/pod/src/ipc/mod.rs new file mode 100644 index 00000000..833b3aea --- /dev/null +++ b/crates/pod/src/ipc/mod.rs @@ -0,0 +1,6 @@ +pub mod event; +pub mod notifier; +pub mod server; + +pub(crate) mod interceptor; +pub(crate) mod notification_buffer; diff --git a/crates/pod/src/notification_buffer.rs b/crates/pod/src/ipc/notification_buffer.rs similarity index 98% rename from crates/pod/src/notification_buffer.rs rename to crates/pod/src/ipc/notification_buffer.rs index 4494287b..19506759 100644 --- a/crates/pod/src/notification_buffer.rs +++ b/crates/pod/src/ipc/notification_buffer.rs @@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex}; use llm_worker::Item; use tracing::warn; -use crate::prompts::{CatalogError, PromptCatalog}; +use crate::prompt::catalog::{CatalogError, PromptCatalog}; /// Maximum queued notifications. Oldest entries are dropped beyond this. const CAPACITY: usize = 128; diff --git a/crates/pod/src/notifier.rs b/crates/pod/src/ipc/notifier.rs similarity index 100% rename from crates/pod/src/notifier.rs rename to crates/pod/src/ipc/notifier.rs diff --git a/crates/pod/src/socket_server.rs b/crates/pod/src/ipc/server.rs similarity index 100% rename from crates/pod/src/socket_server.rs rename to crates/pod/src/ipc/server.rs diff --git a/crates/pod/src/lib.rs b/crates/pod/src/lib.rs index 2274d6b5..736feae6 100644 --- a/crates/pod/src/lib.rs +++ b/crates/pod/src/lib.rs @@ -1,45 +1,30 @@ +pub mod compact; pub mod controller; pub mod hook; -pub mod notifier; -pub mod runtime_dir; -pub mod scope_lock; +pub mod ipc; +pub mod prompt; +pub mod runtime; pub mod shared_state; -pub mod pod_comm_tools; -pub mod pod_events; -pub mod socket_server; -pub mod spawn_pod; -pub mod spawned_pod_registry; +pub mod spawn; -mod agents_md; -mod compact_state; -mod compact_worker; mod factory; mod interrupt_and_run; -mod notification_buffer; mod pod; -mod pod_interceptor; -mod prompt_loader; -mod prompts; -mod prune; -mod system_prompt; -mod token_counter; -mod usage_tracker; - -pub use token_counter::{EstimateSource, SplitPoint, TokenEstimate}; +pub use compact::token_counter::{EstimateSource, SplitPoint, TokenEstimate}; pub use controller::{PodController, PodHandle, ShutdownReceiver}; pub use factory::{FactoryError, PodFactory}; -pub use notifier::Notifier; pub use hook::{Hook, HookEventKind, HookRegistryBuilder}; +pub use ipc::notifier::Notifier; +pub use ipc::server::SocketServer; pub use manifest::{ AuthRef, ModelManifest, PodManifest, PodManifestConfig, PodMetaConfig, Scope, SchemeKind, }; pub use pod::{Pod, PodError, PodRunResult, apply_worker_manifest}; -pub use prompt_loader::PromptLoader; -pub use prompts::{CatalogError, PodPrompt, PromptCatalog}; +pub use prompt::catalog::{CatalogError, PodPrompt, PromptCatalog}; +pub use prompt::loader::PromptLoader; +pub use prompt::system::{SystemPromptContext, SystemPromptError, SystemPromptTemplate}; pub use protocol::{ErrorCode, Event, Method, TurnResult}; pub use provider::{ProviderError, build_client}; -pub use runtime_dir::RuntimeDir; +pub use runtime::dir::RuntimeDir; pub use shared_state::{PodSharedState, PodStatus}; -pub use socket_server::SocketServer; -pub use system_prompt::{SystemPromptContext, SystemPromptError, SystemPromptTemplate}; diff --git a/crates/pod/src/pod.rs b/crates/pod/src/pod.rs index b2c7a829..d36d69ea 100644 --- a/crates/pod/src/pod.rs +++ b/crates/pod/src/pod.rs @@ -13,21 +13,21 @@ use tracing::{info, warn}; use manifest::{PodManifest, PodManifestConfig, ResolveError, Scope, ScopeError, WorkerManifest}; -use crate::agents_md::read_agents_md; -use crate::compact_state::CompactState; +use crate::prompt::agents_md::read_agents_md; +use crate::compact::state::CompactState; use crate::hook::{ Hook, HookRegistryBuilder, OnAbort, OnPromptSubmit, OnTurnEnd, PostToolCall, PreLlmRequest, PreRequestInfo, PreToolCall, }; -use crate::notification_buffer::NotificationBuffer; -use crate::notifier::Notifier; -use crate::pod_interceptor::PodInterceptor; -use crate::prompt_loader::PromptLoader; -use crate::prompts::{CatalogError, PromptCatalog}; -use crate::runtime_dir; -use crate::scope_lock::{self, ScopeAllocationGuard, ScopeLockError}; -use crate::system_prompt::{SystemPromptContext, SystemPromptError, SystemPromptTemplate}; -use crate::usage_tracker::UsageTracker; +use crate::ipc::notification_buffer::NotificationBuffer; +use crate::ipc::notifier::Notifier; +use crate::ipc::interceptor::PodInterceptor; +use crate::prompt::loader::PromptLoader; +use crate::prompt::catalog::{CatalogError, PromptCatalog}; +use crate::runtime::dir; +use crate::runtime::scope_lock::{self, ScopeAllocationGuard, ScopeLockError}; +use crate::prompt::system::{SystemPromptContext, SystemPromptError, SystemPromptTemplate}; +use crate::compact::usage_tracker::UsageTracker; use protocol::{Event, NotificationLevel, NotificationSource}; use tokio::sync::broadcast; use async_trait::async_trait; @@ -848,7 +848,7 @@ impl Pod { pub async fn compact(&mut self, retained_tokens: u64) -> Result { use std::sync::atomic::{AtomicU64, Ordering}; - use crate::compact_worker::{ + use crate::compact::worker::{ CompactWorkerContext, CompactWorkerInterceptor, add_reference_tool, mark_read_required_tool, slice_lines, write_summary_tool, }; @@ -1125,7 +1125,7 @@ impl Pod, St> { // Register this Pod in the machine-wide scope-lock registry // before building anything else, so a spawn that conflicts on // scope fails fast (and without having paid for client setup). - let socket_path = runtime_dir::default_base() + let socket_path = dir::default_base() .map_err(ScopeLockError::from)? .join(&manifest.pod.name) .join("sock"); diff --git a/crates/pod/src/agents_md.rs b/crates/pod/src/prompt/agents_md.rs similarity index 100% rename from crates/pod/src/agents_md.rs rename to crates/pod/src/prompt/agents_md.rs diff --git a/crates/pod/src/prompts.rs b/crates/pod/src/prompt/catalog.rs similarity index 99% rename from crates/pod/src/prompts.rs rename to crates/pod/src/prompt/catalog.rs index d7a5741b..64801239 100644 --- a/crates/pod/src/prompts.rs +++ b/crates/pod/src/prompt/catalog.rs @@ -45,13 +45,13 @@ use serde::Deserialize; use thiserror::Error; use tracing::warn; -use crate::prompt_loader::PromptLoader; +use crate::prompt::loader::PromptLoader; // Generated by build.rs from `resources/prompts/internal.toml`. include!(concat!(env!("OUT_DIR"), "/internal_keys.rs")); /// Source of the builtin pack. Baked in at compile time. -const INTERNAL_TOML: &str = include_str!("../../../resources/prompts/internal.toml"); +const INTERNAL_TOML: &str = include_str!("../../../../resources/prompts/internal.toml"); /// Pod-level prompt injection point. /// diff --git a/crates/pod/src/prompt_loader.rs b/crates/pod/src/prompt/loader.rs similarity index 99% rename from crates/pod/src/prompt_loader.rs rename to crates/pod/src/prompt/loader.rs index c27590ec..7f956e13 100644 --- a/crates/pod/src/prompt_loader.rs +++ b/crates/pod/src/prompt/loader.rs @@ -104,7 +104,7 @@ pub enum LoaderError { /// libraries. Cheap to clone. /// /// Also carries the auto-discovered `prompts.toml` pack file paths so -/// [`crate::prompts::PromptCatalog`] can read the same user/workspace +/// [`crate::prompt::catalog::PromptCatalog`] can read the same user/workspace /// layers without a separate plumbing channel. These fields do not /// affect `$prefix` asset resolution — they are purely metadata /// consulted by the catalog loader. diff --git a/crates/pod/src/prompt/mod.rs b/crates/pod/src/prompt/mod.rs new file mode 100644 index 00000000..ae52e7ca --- /dev/null +++ b/crates/pod/src/prompt/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod agents_md; +pub(crate) mod catalog; +pub(crate) mod loader; +pub(crate) mod system; diff --git a/crates/pod/src/system_prompt.rs b/crates/pod/src/prompt/system.rs similarity index 99% rename from crates/pod/src/system_prompt.rs rename to crates/pod/src/prompt/system.rs index 7397cc3c..1aed964c 100644 --- a/crates/pod/src/system_prompt.rs +++ b/crates/pod/src/prompt/system.rs @@ -22,8 +22,8 @@ use minijinja::value::Value; use minijinja::{Environment, ErrorKind, UndefinedBehavior}; use thiserror::Error; -use crate::prompt_loader::{LoaderError, PromptLoader, PromptRef}; -use crate::prompts::{CatalogError, PromptCatalog}; +use crate::prompt::loader::{LoaderError, PromptLoader, PromptRef}; +use crate::prompt::catalog::{CatalogError, PromptCatalog}; #[derive(Debug, Error)] pub enum SystemPromptError { diff --git a/crates/pod/src/runtime_dir.rs b/crates/pod/src/runtime/dir.rs similarity index 100% rename from crates/pod/src/runtime_dir.rs rename to crates/pod/src/runtime/dir.rs diff --git a/crates/pod/src/runtime/mod.rs b/crates/pod/src/runtime/mod.rs new file mode 100644 index 00000000..d47100ae --- /dev/null +++ b/crates/pod/src/runtime/mod.rs @@ -0,0 +1,2 @@ +pub mod dir; +pub mod scope_lock; diff --git a/crates/pod/src/scope_lock.rs b/crates/pod/src/runtime/scope_lock.rs similarity index 100% rename from crates/pod/src/scope_lock.rs rename to crates/pod/src/runtime/scope_lock.rs diff --git a/crates/pod/src/pod_comm_tools.rs b/crates/pod/src/spawn/comm_tools.rs similarity index 99% rename from crates/pod/src/pod_comm_tools.rs rename to crates/pod/src/spawn/comm_tools.rs index f3e29e04..0571d333 100644 --- a/crates/pod/src/pod_comm_tools.rs +++ b/crates/pod/src/spawn/comm_tools.rs @@ -21,9 +21,9 @@ use protocol::{ErrorCode, Event, Method}; use serde::Deserialize; use tokio::net::UnixStream; -use crate::runtime_dir::SpawnedPodRecord; -use crate::scope_lock::{self, LockFileGuard}; -use crate::spawned_pod_registry::SpawnedPodRegistry; +use crate::runtime::dir::SpawnedPodRecord; +use crate::runtime::scope_lock::{self, LockFileGuard}; +use crate::spawn::registry::SpawnedPodRegistry; /// Timeout applied to each socket-level operation — connect, write, /// read. Kept short so a stuck child doesn't block the spawner's turn. diff --git a/crates/pod/src/spawn/mod.rs b/crates/pod/src/spawn/mod.rs new file mode 100644 index 00000000..a60b0ea5 --- /dev/null +++ b/crates/pod/src/spawn/mod.rs @@ -0,0 +1,3 @@ +pub mod comm_tools; +pub mod registry; +pub mod tool; diff --git a/crates/pod/src/spawned_pod_registry.rs b/crates/pod/src/spawn/registry.rs similarity index 98% rename from crates/pod/src/spawned_pod_registry.rs rename to crates/pod/src/spawn/registry.rs index 57a86b99..ecf366fc 100644 --- a/crates/pod/src/spawned_pod_registry.rs +++ b/crates/pod/src/spawn/registry.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use tokio::sync::Mutex; -use crate::runtime_dir::{RuntimeDir, SpawnedPodRecord}; +use crate::runtime::dir::{RuntimeDir, SpawnedPodRecord}; pub struct SpawnedPodRegistry { records: Mutex>, diff --git a/crates/pod/src/spawn_pod.rs b/crates/pod/src/spawn/tool.rs similarity index 98% rename from crates/pod/src/spawn_pod.rs rename to crates/pod/src/spawn/tool.rs index 1c2bfe7b..9cee8d75 100644 --- a/crates/pod/src/spawn_pod.rs +++ b/crates/pod/src/spawn/tool.rs @@ -24,10 +24,10 @@ use tokio::net::UnixStream; use tokio::process::Command; use tokio::time::sleep; -use crate::pod_events; -use crate::runtime_dir::SpawnedPodRecord; -use crate::scope_lock::{self, LockFileGuard, ScopeLockError}; -use crate::spawned_pod_registry::SpawnedPodRegistry; +use crate::ipc::event; +use crate::runtime::dir::SpawnedPodRecord; +use crate::runtime::scope_lock::{self, LockFileGuard, ScopeLockError}; +use crate::spawn::registry::SpawnedPodRegistry; use protocol::PodEvent; const DESCRIPTION: &str = "Spawn a new Pod process to work on a delegated task. \ @@ -233,7 +233,7 @@ impl Tool for SpawnPodTool { // Notify this Pod's own parent so the grandparent can register // the new grandchild directly. Fire-and-forget; top-level Pods // (with no parent) skip the send inside `fire_and_forget`. - pod_events::fire_and_forget( + event::fire_and_forget( self.parent_socket.clone(), PodEvent::ScopeSubDelegated { parent_pod: self.spawner_name.clone(), diff --git a/crates/pod/tests/pod_comm_tools_test.rs b/crates/pod/tests/pod_comm_tools_test.rs index f570bd74..8982641e 100644 --- a/crates/pod/tests/pod_comm_tools_test.rs +++ b/crates/pod/tests/pod_comm_tools_test.rs @@ -14,12 +14,12 @@ use std::sync::{Arc, LazyLock, Mutex}; use llm_worker::llm_client::types::{ContentPart, Item, Role}; use llm_worker::tool::ToolOutput; use manifest::{Permission, ScopeRule}; -use pod::pod_comm_tools::{ +use pod::spawn::comm_tools::{ list_pods_tool, read_pod_output_tool, send_to_pod_tool, stop_pod_tool, }; -use pod::runtime_dir::{RuntimeDir, SpawnedPodRecord}; -use pod::scope_lock::{self, LockFileGuard}; -use pod::spawned_pod_registry::SpawnedPodRegistry; +use pod::runtime::dir::{RuntimeDir, SpawnedPodRecord}; +use pod::runtime::scope_lock::{self, LockFileGuard}; +use pod::spawn::registry::SpawnedPodRegistry; use protocol::stream::{JsonLineReader, JsonLineWriter}; use protocol::{ErrorCode, Event, Greeting, Method}; use serde_json::json; diff --git a/crates/pod/tests/pod_events_test.rs b/crates/pod/tests/pod_events_test.rs index b0550ac5..a461d7e4 100644 --- a/crates/pod/tests/pod_events_test.rs +++ b/crates/pod/tests/pod_events_test.rs @@ -9,10 +9,10 @@ use std::path::PathBuf; use std::sync::{Arc, LazyLock, Mutex}; use std::time::Duration; -use pod::pod_events::{apply_event_side_effects, fire_and_forget, render_event}; -use pod::runtime_dir::{RuntimeDir, SpawnedPodRecord}; -use pod::scope_lock::{self, LockFileGuard}; -use pod::spawned_pod_registry::SpawnedPodRegistry; +use pod::ipc::event::{apply_event_side_effects, fire_and_forget, render_event}; +use pod::runtime::dir::{RuntimeDir, SpawnedPodRecord}; +use pod::runtime::scope_lock::{self, LockFileGuard}; +use pod::spawn::registry::SpawnedPodRegistry; use protocol::stream::JsonLineReader; use protocol::{Method, Permission, PodEvent, ScopeRule}; use tempfile::TempDir; diff --git a/crates/pod/tests/spawn_pod_test.rs b/crates/pod/tests/spawn_pod_test.rs index d741a07b..a8e307c7 100644 --- a/crates/pod/tests/spawn_pod_test.rs +++ b/crates/pod/tests/spawn_pod_test.rs @@ -12,10 +12,10 @@ use std::sync::{LazyLock, Mutex}; use llm_worker::tool::{ToolError, ToolOutput}; use manifest::{AuthRef, ModelManifest, Permission, SchemeKind, ScopeRule}; -use pod::runtime_dir::{RuntimeDir, SpawnedPodRecord}; -use pod::scope_lock::{self, LockFileGuard}; -use pod::spawn_pod::spawn_pod_tool; -use pod::spawned_pod_registry::SpawnedPodRegistry; +use pod::runtime::dir::{RuntimeDir, SpawnedPodRecord}; +use pod::runtime::scope_lock::{self, LockFileGuard}; +use pod::spawn::tool::spawn_pod_tool; +use pod::spawn::registry::SpawnedPodRegistry; use protocol::Method; use protocol::stream::JsonLineReader; use serde_json::json; diff --git a/tickets/pod-module-layout.md b/tickets/pod-module-layout.md new file mode 100644 index 00000000..a915732e --- /dev/null +++ b/tickets/pod-module-layout.md @@ -0,0 +1,97 @@ +# pod クレートのモジュール分割 + +## 背景 + +`crates/pod/src/` は 25 ファイル・約 10k 行がフラットに並んでおり、責務の軸が立っていない。命名も `pod_events` / `pod_interceptor` / `pod_comm_tools` のように、所属クレート名である `pod_` prefix を冗長に付けているものと、`spawn_pod` / `spawned_pod_registry` のように prefix なしのものが混在しており、どの単位で読むべきかが構造から読み取れない。 + +同規模の `llm-worker` は `llm_client/scheme/{anthropic,gemini,...}/` と `timeline/` でサブモジュール化されており、`provider` も `codex_oauth/` を切っている。`pod` だけが規模に対して階層を持っていない。 + +## 要件 + +### 目標レイアウト + +``` +crates/pod/src/ +├── lib.rs +├── main.rs +├── pod.rs ← Pod 本体 +├── controller.rs ← PodController / PodHandle +├── factory.rs ← manifest cascade からの組み立て +├── shared_state.rs ← 横断的な共有状態 +│ +├── ipc/ ← Pod ↔ クライアント(自 Pod の対外 socket) +│ ├── mod.rs +│ ├── server.rs ← socket_server.rs +│ ├── event.rs ← pod_events.rs +│ ├── interceptor.rs ← pod_interceptor.rs +│ ├── notifier.rs +│ └── notification_buffer.rs +│ +├── spawn/ ← Pod ↔ Pod(親子関係とツール群) +│ ├── mod.rs +│ ├── tool.rs ← spawn_pod.rs +│ ├── registry.rs ← spawned_pod_registry.rs +│ └── comm_tools.rs ← pod_comm_tools.rs +│ +├── prompt/ +│ ├── mod.rs +│ ├── catalog.rs ← prompts.rs +│ ├── loader.rs ← prompt_loader.rs +│ ├── system.rs ← system_prompt.rs +│ └── agents_md.rs +│ +├── compact/ +│ ├── mod.rs +│ ├── state.rs ← compact_state.rs +│ ├── worker.rs ← compact_worker.rs +│ ├── token_counter.rs +│ ├── usage_tracker.rs +│ └── prune.rs +│ +├── hook.rs ← フック基盤(将来拡張予定だが現在 1 ファイルなのでフラットに残す) +├── interrupt_and_run.rs ← `impl Pod` の付加メソッド(pod.rs の延長) +│ +└── runtime/ + ├── mod.rs + ├── dir.rs ← runtime_dir.rs + └── scope_lock.rs +``` + +### 軸の置き方 + +- **`ipc/` と `spawn/` を分離**する: 前者は「自 Pod 外界(クライアント)との通信」、後者は「他 Pod(子)との通信」。対向が違うので混ぜない。 +- **`compact/` に会計系を集約**する: `token_counter` / `usage_tracker` / `prune` は compaction の判断材料として利用側から見ると一塊。 +- **トップレベルに残すのは核 4 個**: `pod` / `controller` / `factory` / `shared_state`。どのサブに入れても不自然になる「束ねる側」。 +- **`pod_` prefix を全廃**する: `pod` crate 内で `pod::pod_events` は冗長。`pod::ipc::event` にする。 + +### 公開 API パスの変化 + +`lib.rs` の `pub mod` 経由で露出している型は外部からパスが変わる。例: + +| 旧 | 新 | +|---|---| +| `pod::socket_server::SocketServer` | `pod::ipc::server::SocketServer` | +| `pod::pod_events::{...}` | `pod::ipc::event::{...}` | +| `pod::pod_comm_tools::{...}` | `pod::spawn::comm_tools::{...}` | +| `pod::spawn_pod::spawn_pod_tool` | `pod::spawn::tool::spawn_pod_tool` | +| `pod::spawned_pod_registry::SpawnedPodRegistry` | `pod::spawn::registry::SpawnedPodRegistry` | +| `pod::runtime_dir::{...}` | `pod::runtime::dir::{...}` | +| `pod::scope_lock::{...}` | `pod::runtime::scope_lock::{...}` | + +現時点で `pod::XXX` パスを参照しているのは `crates/pod/tests/` のみで、他クレートからの `use pod::` 参照は無い(`manifest` / `llm-worker` 内の該当箇所はコメントのみ)。 + +### スコープ外 + +- `pod.rs`(1496 行)・`scope_lock.rs`(1103 行)自体の内部分割は別チケット。本チケットは **ファイルの配置と命名の変更のみ** を扱い、1 ファイルの中身は移動だけに留める。 +- `lib.rs` の `pub use` re-export で旧パスを残す互換レイヤは作らない(外部参照が tests のみで、一斉更新できるため)。 + +### 完了条件 + +- 新構造でビルドが通る (`cargo check`) +- `crates/pod/tests/` が全部緑 (`cargo test -p pod`) +- 既存の振る舞いに変更なし(純粋な再配置) + +## Review +- 状態: Approve with follow-up +- レビュー詳細: [./pod-module-layout.review.md](./pod-module-layout.review.md) +- 日付: 2026-04-24 diff --git a/tickets/pod-module-layout.review.md b/tickets/pod-module-layout.review.md new file mode 100644 index 00000000..4b2c8ddf --- /dev/null +++ b/tickets/pod-module-layout.review.md @@ -0,0 +1,41 @@ +# Review: pod クレートのモジュール分割 + +## 前提・要件の確認 + +- **目標レイアウト**: ほぼチケット通り。`crates/pod/src/` 直下は `pod.rs` / `controller.rs` / `factory.rs` / `shared_state.rs` / `hook.rs` / `interrupt_and_run.rs` / `lib.rs` / `main.rs` のみとなり、`ipc/` / `spawn/` / `prompt/` / `compact/` / `runtime/` の 5 つのサブディレクトリに責務別に移動されている(`crates/pod/src/` ls 結果 + 各 `mod.rs` で確認)。 +- **`ipc/` と `spawn/` の分離**: 自 Pod 対外 socket 系は `ipc/` (`server`, `event`, `interceptor`, `notifier`, `notification_buffer`)、親子関係系は `spawn/` (`tool`, `registry`, `comm_tools`) に分かれており、対向別の軸が保たれている。 +- **`compact/` への会計系集約**: `state` / `worker` / `token_counter` / `usage_tracker` / `prune` がすべて `compact/` に収まっている。 +- **`pod_` prefix 全廃**: `pod_events` → `ipc::event`、`pod_interceptor` → `ipc::interceptor`、`pod_comm_tools` → `spawn::comm_tools` に改名済み。`spawn_pod` / `spawned_pod_registry` も `spawn::tool` / `spawn::registry` に短縮。ソース・テストともに旧名は残っていない(grep 確認済み)。 +- **公開 API パスの変化**: チケット表通りに移動済み。`crates/pod/tests/` 側も 3 本(`pod_comm_tools_test.rs` / `pod_events_test.rs` / `spawn_pod_test.rs`)を新パスに一斉更新。`pod::spawn::tool::spawn_pod_tool` 等は `pub mod` を経由して到達可能。 +- **スコープ外(ファイル内部分割なし)**: `pod.rs` / `scope_lock.rs` の中身には手が入っておらず、差分は `use crate::...` の書き換えに限定されている(`git diff` 確認)。 +- **完了条件**: `cargo check -p pod` 緑、`cargo test --workspace` が 558 件緑との申告とも整合(依存側・ビルド側でも stale な参照なし)。 + +## アーキテクチャ・スコープ + +- **レイヤ境界**: 新しい軸は「自 Pod の外界(ipc)」「他 Pod(spawn)」「プロンプト」「会計・圧縮」「ランタイム配置」という独立した関心事で、相互依存は必要最小限(`ipc::event` → `runtime::scope_lock`、`ipc::interceptor` → `compact::token_counter` / `compact::state` など)。責務軸が立ち、`llm-worker` の `llm_client/scheme/` 等と釣り合う粒度になった。 +- **純粋な再配置か**: 差分は (1) 物理移動、(2) `use crate::...` の書き換え、(3) `pod.rs` 内 1 箇所の `runtime_dir::default_base` → `dir::default_base`、(4) `interrupt_and_run.rs` の `#[cfg(test)] use` のパス、(5) `prompts.rs` の `include_str!("../../../...")` を 1 階層ぶんだけ `../../../../...` に延長(`realpath` で同一ファイルを指すことを確認)— の範囲に留まっている。振る舞い変化は混ざっていない。 +- **hook/ サブディレクトリの取りやめ**: 現状 `hook.rs` が単体で ~200 行程度、かつ `interrupt_and_run.rs` は `impl Pod` の拡張メソッドであり `hook` の仲間ではない、という判断は妥当。無理にディレクトリを作ると「中身 1 ファイル+意味の異なる隣人」という歪な構造になる。ticket 本文の現行版(行 51–52)は新判断と整合しており、乖離はない。 +- **コードベースを歪める変更**: なし。新構造のために本体のロジックを曲げた箇所や、無駄な中間層の追加は見当たらない。 +- **依存追加**: 本チケットでは外部 crate の追加はなく、`cargo add` ルールの対象外。 + +## 指摘事項 + +### Non-blocking / Follow-up + +- **`pub mod` / `mod` の区別が一部で広がっている** — `crates/pod/src/lib.rs` と各 `mod.rs`。旧 `lib.rs` では次の 5 個は `mod ...;`(crate-private)だった: + - `agents_md`, `prompt_loader`, `prompts`, `system_prompt`, `token_counter` + + 新レイアウトではそれぞれ `prompt/mod.rs` の `pub mod agents_md;` / `pub mod loader;` / `pub mod catalog;` / `pub mod system;` と `compact/mod.rs` の `pub mod token_counter;` になっており、`pod::prompt::catalog::PromptCatalog` のような深いパスが外部から到達可能になった(ルート `pub use` で代表型は既に公開されているため、副作用は「外から深いパスが見える」だけ)。 + + 実害は発見できなかった(`grep -rn "pod::prompt\|pod::compact::token"` は 0 ヒット、利用側は全てルート再エクスポート経由)が、ユーザが質問 #2 で述べている「旧 lib.rs での `pub mod` / `mod` 区別を保持」という意図に厳密に従うなら、上記 5 個は `pub(crate) mod ...;` に落とす方が素直。現に `ipc::notification_buffer` / `ipc::interceptor` / `compact::state` / `compact::worker` / `compact::usage_tracker` / `compact::prune` については `pub(crate) mod` で区別を維持できており、プロンプト系だけが揃っていない。テスト側も `pod::PromptLoader` 等のルート経由で参照しているので、`pub(crate) mod` に絞っても既存テストは影響を受けないはず。 + + 別チケット or 本チケット内の微修正のどちらでも対応可能。 + +### Nits + +- **`lib.rs` の `pub use` ブロック内で `manifest`/`protocol`/`provider` の外部 crate 再エクスポートが自 crate モジュールの間に挟まっている** — `crates/pod/src/lib.rs:14-30`。旧版でも同様に混在していたので新規に悪化したわけではないが、責務軸で整理する今回のタイミングで「自 crate → 外部 crate」の順にグルーピングしても読みやすい。強い指摘ではない。 +- **`pod.rs` 先頭 `use` の順序** — `crates/pod/src/pod.rs:16-30`。`crate::prompt::agents_md`, `crate::compact::state`, `crate::ipc::...`, `crate::prompt::...`, `crate::runtime::...`, `crate::compact::usage_tracker` がサブモジュール別にまとまっておらず行きつ戻りつしている。機能的には無害だが、rustfmt では並び替わらない範囲なので任意で整頓する余地がある。 + +## 判断 + +**Approve with follow-up** — チケット本文の要件(配置・命名・prefix 全廃・スコープ外保持)と完了条件(ビルド緑・テスト緑・挙動不変)はすべて満たされている。hook/ 取りやめの判断変更も合理的で本文と整合。唯一、旧 `lib.rs` での `mod`(crate-private)を保持するなら `prompt/` と `compact::token_counter` の 5 モジュールを `pub(crate) mod` に狭める余地があるが、これはブロッキングではない。