diff --git a/Cargo.lock b/Cargo.lock index 2eeb74bb4..89372a8b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3232,13 +3232,13 @@ dependencies = [ "op-figma", "op-git", "op-host-native", + "op-host-services", "op-i18n", "op-mcp", "op-opmerge", "op-orchestrator", "op-pen-loader", "op-process-io", - "op-web-daemon", "reqwest 0.12.28", "rfd", "serde", @@ -3275,6 +3275,40 @@ dependencies = [ "tracing-test", ] +[[package]] +name = "op-host-services" +version = "0.8.0" +dependencies = [ + "agent", + "anthropic-agent-sdk", + "async-trait", + "base64", + "dirs 5.0.1", + "futures", + "github-copilot-sdk", + "glam", + "jian-ops-schema", + "op-acp", + "op-ai", + "op-ai-skills", + "op-design-lint", + "op-editor-core", + "op-editor-host-core", + "op-editor-ui", + "op-host-native", + "op-i18n", + "op-mcp", + "op-orchestrator", + "op-pen-loader", + "op-process-io", + "reqwest 0.12.28", + "serde", + "serde_json", + "skia-safe", + "tokio", + "zip", +] + [[package]] name = "op-host-web" version = "0.8.0" @@ -3302,7 +3336,7 @@ dependencies = [ name = "op-host-web-server" version = "0.8.0" dependencies = [ - "op-web-daemon", + "op-host-services", ] [[package]] @@ -3389,40 +3423,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "op-web-daemon" -version = "0.8.0" -dependencies = [ - "agent", - "anthropic-agent-sdk", - "async-trait", - "base64", - "dirs 5.0.1", - "futures", - "github-copilot-sdk", - "glam", - "jian-ops-schema", - "op-acp", - "op-ai", - "op-ai-skills", - "op-design-lint", - "op-editor-core", - "op-editor-host-core", - "op-editor-ui", - "op-host-native", - "op-i18n", - "op-mcp", - "op-orchestrator", - "op-pen-loader", - "op-process-io", - "reqwest 0.12.28", - "serde", - "serde_json", - "skia-safe", - "tokio", - "zip", -] - [[package]] name = "op-web-sdk" version = "0.8.0" diff --git a/Dockerfile.web-rust b/Dockerfile.web-rust index 74846eb9c..82f51b10d 100644 --- a/Dockerfile.web-rust +++ b/Dockerfile.web-rust @@ -47,7 +47,7 @@ ARG WEB_BUNDLE_SOURCE=build ARG SERVE_PORT=3100 # binaryen -> wasm-opt -Oz ; nodejs -> the 0-env-import assert in the gate -# script. op-host-web-server is the headless raster daemon (links op-web-daemon +# script. op-host-web-server is the headless raster daemon (links op-host-services # only — no winit/glutin/skia-GL under Approach Y), so NONE of the GL/X11 # link-time libs the desktop binary needed are required here; only # freetype/fontconfig (+ CJK fonts) for skia's raster text shaping at export. @@ -68,7 +68,7 @@ WORKDIR /src COPY . . # Build the headless web/MCP server binary (the `--serve-web` host) — links -# op-web-daemon only, no winit/skia-GL. Always built; this is the runtime +# op-host-services only, no winit/skia-GL. Always built; this is the runtime # binary regardless of how the web bundle is produced. RUN cargo build -p op-host-web-server --release diff --git a/crates/op-host-desktop/Cargo.toml b/crates/op-host-desktop/Cargo.toml index a91ddb174..3d9435e79 100644 --- a/crates/op-host-desktop/Cargo.toml +++ b/crates/op-host-desktop/Cargo.toml @@ -65,8 +65,8 @@ op-editor-ui = { path = "../op-editor-ui" } op-host-native = { path = "../op-host-native", version = "0.8.0", features = ["gl-host"] } # Phase 2: the headless daemon crate. The `--serve-web` closure migrates here # over Phases 2-5; op-host-desktop's serve-web dispatch + GUI residuals call into -# it via `op_web_daemon::*`. -op-web-daemon = { path = "../op-web-daemon" } +# it via `op_host_services::*`. +op-host-services = { path = "../op-host-services" } # Accessibility (#67) — the platform-free `accesskit` core types # (`TreeUpdate` / `ActionRequest` / `ActivationHandler` …) used by the # desktop a11y adapter (`src/a11y.rs`). The OS-specific subclassing diff --git a/crates/op-host-desktop/src/acp_agent_probe_host.rs b/crates/op-host-desktop/src/acp_agent_probe_host.rs index b893c9a80..13d4a9781 100644 --- a/crates/op-host-desktop/src/acp_agent_probe_host.rs +++ b/crates/op-host-desktop/src/acp_agent_probe_host.rs @@ -1,7 +1,7 @@ //! Host pump for connect-time ACP agent probes. //! //! The job struct + probe fns live in -//! [`op_web_daemon::acp_agent_probe_host`] (codex Issue 5 — the job is +//! [`op_host_services::acp_agent_probe_host`] (codex Issue 5 — the job is //! a `DesktopApp` field); this residual keeps only the `impl DesktopApp` //! pump, which drives the job through its public API. @@ -10,7 +10,7 @@ use op_editor_core::agent_settings::AcpAgentConnectOutcome; use crate::DesktopApp; // Re-export so `crate::acp_agent_probe_host::AcpAgentConnectJob` (the // `DesktopApp` field type in `main.rs`) still resolves with zero churn. -pub use op_web_daemon::acp_agent_probe_host::AcpAgentConnectJob; +pub use op_host_services::acp_agent_probe_host::AcpAgentConnectJob; impl DesktopApp { pub(crate) fn drain_acp_agent_connect(&mut self) -> bool { @@ -92,7 +92,7 @@ impl DesktopApp { mod tests { use super::*; use op_editor_core::agent_settings::{AcpAgentConnectPhase, AcpConnectionType}; - use op_web_daemon::acp_agent_probe_host::AcpAgentProbeOutcome; + use op_host_services::acp_agent_probe_host::AcpAgentProbeOutcome; use std::collections::BTreeMap; #[test] diff --git a/crates/op-host-desktop/src/app_handler.rs b/crates/op-host-desktop/src/app_handler.rs index 94de39afb..9ac26349e 100644 --- a/crates/op-host-desktop/src/app_handler.rs +++ b/crates/op-host-desktop/src/app_handler.rs @@ -255,7 +255,7 @@ impl ApplicationHandler for DesktopApp { // background Figma import worker so the launch doesn't freeze // on a multi-second parse. if let Some(path) = self.initial_file.take() { - if op_web_daemon::doc_io::is_supported_figma_import(&path) { + if op_host_services::doc_io::is_supported_figma_import(&path) { figma_import_session::cancel(&mut self.host, &mut self.current_figma_import); self.current_figma_import = Some(figma_import_session::spawn(&mut self.host, path)); self.request_redraw(true); @@ -281,7 +281,7 @@ impl ApplicationHandler for DesktopApp { // one-off `op start` never rewrites the user's saved settings. let bootstrap_changed = self.bootstrap_mcp_runtime_from_settings(); if bootstrap_changed && self.force_live_mcp_port.is_none() { - op_web_daemon::settings_io::save(self.host.editor_state()); + op_host_services::settings_io::save(self.host.editor_state()); } // Publish (or clean up) the live MCP discovery file now that the // launch-time server state is settled, so `op` can find this canvas. @@ -347,7 +347,7 @@ impl ApplicationHandler for DesktopApp { // on the trackpad hot path. let settings_before = match &event { WindowEvent::CursorMoved { .. } => None, - _ => Some(op_web_daemon::settings_io::fingerprint(self.host.editor_state())), + _ => Some(op_host_services::settings_io::fingerprint(self.host.editor_state())), }; let mcp_cli_before = match &event { WindowEvent::CursorMoved { .. } => None, @@ -361,7 +361,7 @@ impl ApplicationHandler for DesktopApp { // The unsaved-changes prompt can abort the close. if self.confirm_close() { self.host.flush_settings_input(); - op_web_daemon::settings_io::save(self.host.editor_state()); + op_host_services::settings_io::save(self.host.editor_state()); event_loop.exit(); } } @@ -438,12 +438,12 @@ impl ApplicationHandler for DesktopApp { // dashboards, so doing it inline would freeze the // window). Anything else is ignored silently so a // stray drop can't disrupt the current document. - if op_web_daemon::doc_io::is_supported_figma_import(&path) { + if op_host_services::doc_io::is_supported_figma_import(&path) { figma_import_session::cancel(&mut self.host, &mut self.current_figma_import); self.current_figma_import = Some(figma_import_session::spawn(&mut self.host, path)); self.request_redraw(true); - } else if op_web_daemon::doc_io::is_supported_document(&path) { + } else if op_host_services::doc_io::is_supported_document(&path) { if persistence::open_path( &mut self.host, path, @@ -872,7 +872,7 @@ impl ApplicationHandler for DesktopApp { // — a bare `exit()` would drop work. if self.confirm_close() { self.host.flush_settings_input(); - op_web_daemon::settings_io::save(self.host.editor_state()); + op_host_services::settings_io::save(self.host.editor_state()); event_loop.exit(); } } @@ -998,13 +998,13 @@ impl ApplicationHandler for DesktopApp { // in-flight Figma import internally, so a // stale worker can't overwrite the fresh // document when its result lands. - op_web_daemon::doc_io::ActionOutcome::Saved => self.mark_document_saved(), + op_host_services::doc_io::ActionOutcome::Saved => self.mark_document_saved(), // User picked a `.fig`; spin up the worker // session and let `pump` apply the document // once parsing finishes. Cancel any prior // in-flight session first so two imports // in quick succession don't race. - op_web_daemon::doc_io::ActionOutcome::FigmaImportStarted(path) => { + op_host_services::doc_io::ActionOutcome::FigmaImportStarted(path) => { figma_import_session::cancel( &mut self.host, &mut self.current_figma_import, @@ -1013,7 +1013,7 @@ impl ApplicationHandler for DesktopApp { Some(figma_import_session::spawn(&mut self.host, path)); self.request_redraw(true); } - op_web_daemon::doc_io::ActionOutcome::Noop => {} + op_host_services::doc_io::ActionOutcome::Noop => {} } } } @@ -1219,7 +1219,7 @@ impl ApplicationHandler for DesktopApp { self.request_redraw(true); } if let Some(before) = settings_before { - op_web_daemon::settings_io::save_if_changed(self.host.editor_state(), before); + op_host_services::settings_io::save_if_changed(self.host.editor_state(), before); } // A Git-panel click or Enter may have queued an action // (Commit / Refresh / Pull) — run it after the event. @@ -1246,7 +1246,7 @@ impl ApplicationHandler for DesktopApp { // `CloseRequested`; flush MCP port draft before snapshotting so // a focused-but-uncommitted edit isn't silently dropped. self.host.flush_settings_input(); - op_web_daemon::settings_io::save(self.host.editor_state()); + op_host_services::settings_io::save(self.host.editor_state()); if let Some(mut server) = self.mcp_server.take() { server.stop(); crate::mcp_port_file::remove(); diff --git a/crates/op-host-desktop/src/chat_acp.rs b/crates/op-host-desktop/src/chat_acp.rs index 7feac25a3..9c61360c0 100644 --- a/crates/op-host-desktop/src/chat_acp.rs +++ b/crates/op-host-desktop/src/chat_acp.rs @@ -49,8 +49,8 @@ use op_acp::{ use op_ai::chat_provider::{ChatDelta, ChatProvider, ChatRequest, EffortLevel, StopReason}; use tokio::sync::mpsc; -use op_web_daemon::chat_attachment::TempGuard; -use op_web_daemon::chat_runtime::{shared_runtime, BlockingRecvIter}; +use op_host_services::chat_attachment::TempGuard; +use op_host_services::chat_runtime::{shared_runtime, BlockingRecvIter}; /// TS error surfaced when ACP is used with the MCP server stopped — /// ported verbatim from `apps/web/server/api/ai/agent.ts:506-511`. @@ -178,12 +178,12 @@ impl ChatProvider for AcpProvider { // paths. The thinking / effort knobs ride in-band either way // (ACP exposes no dedicated reasoning channel). let (mut prompt, guard) = if config.connection_type == ConnectionType::Local { - match op_web_daemon::chat_attachment::prompt_with_attachments( + match op_host_services::chat_attachment::prompt_with_attachments( &request.user_message, &request.attachments, ) { Ok(pair) => pair, - Err(e) => return op_web_daemon::chat_attachment::attachment_error_turn(e), + Err(e) => return op_host_services::chat_attachment::attachment_error_turn(e), } } else { let mut prompt = request.user_message.clone(); @@ -197,7 +197,7 @@ impl ChatProvider for AcpProvider { (prompt, None) }; let mut directive = String::new(); - if let Some(d) = op_web_daemon::chat_attachment::thinking_directive(request.thinking) { + if let Some(d) = op_host_services::chat_attachment::thinking_directive(request.thinking) { directive.push_str(d); } if request.effort != EffortLevel::Low { diff --git a/crates/op-host-desktop/src/chat_attachment.rs b/crates/op-host-desktop/src/chat_attachment.rs index 96b768d67..a4869e916 100644 --- a/crates/op-host-desktop/src/chat_attachment.rs +++ b/crates/op-host-desktop/src/chat_attachment.rs @@ -2,14 +2,14 @@ //! //! The headless attachment helpers (base64, temp-file spill, prompt //! building, the Claude image-Read flow) live in -//! [`op_web_daemon::chat_attachment`]; this module keeps only the +//! [`op_host_services::chat_attachment`]; this module keeps only the //! native file picker that stages a chosen image on the chat state. use std::fs; use op_editor_core::chat::{ChatAttachment, MAX_ATTACHMENT_BYTES}; use op_host_native::WidgetHostNative; -use op_web_daemon::chat_attachment::media_type_for_path; +use op_host_services::chat_attachment::media_type_for_path; /// Drain `chat.pending_attachment_pick` (raised by the attach /// button): open a native image picker and stage the chosen file on diff --git a/crates/op-host-desktop/src/chat_intent_host_tests.rs b/crates/op-host-desktop/src/chat_intent_host_tests.rs index 8b0270cb5..b3e2173d0 100644 --- a/crates/op-host-desktop/src/chat_intent_host_tests.rs +++ b/crates/op-host-desktop/src/chat_intent_host_tests.rs @@ -1,12 +1,12 @@ //! Host-coupled test for the CLI standard-mode design route (GAP #33). //! //! The bulk of `chat_intent`'s tests are headless and live beside the -//! module in `op_web_daemon::chat_intent` (accessing its `#[cfg(test)]` +//! module in `op_host_services::chat_intent` (accessing its `#[cfg(test)]` //! internals via `super`). This one test is the exception: it drives the //! desktop GUI design-session pumps (`design_session::{pump_commands, //! pump_progress}`, which take `&mut WidgetHostNative` — orphan rule) to //! prove the new-design route clears the agent frame indicators after the -//! turn finishes. `op-web-daemon` links `op-host-native` with +//! turn finishes. `op-host-services` links `op-host-native` with //! default-features off, so `WidgetHostNative` (gl-host gated) is absent //! there; the test therefore lives host-side. @@ -20,14 +20,14 @@ use op_orchestrator::DesignRequest; use op_editor_host_core::design::DesignSession; use op_host_native::WidgetHostNative; -use op_web_daemon::chat_canvas_tools::chat_tool_channel; -use op_web_daemon::chat_intent::{run_cli_turn, CliTurnPlan}; +use op_host_services::chat_canvas_tools::chat_tool_channel; +use op_host_services::chat_intent::{run_cli_turn, CliTurnPlan}; use crate::design_session::{pump_commands, pump_progress}; // --------------------------------------------------------------------------- // Scripted providers (copied from the headless chat_intent test sibling — -// `op-web-daemon` keeps its own copies for the cross-crate tests there). +// `op-host-services` keeps its own copies for the cross-crate tests there). // --------------------------------------------------------------------------- struct Scripted { diff --git a/crates/op-host-desktop/src/chat_session.rs b/crates/op-host-desktop/src/chat_session.rs index b8c8cd9ad..9444ffe54 100644 --- a/crates/op-host-desktop/src/chat_session.rs +++ b/crates/op-host-desktop/src/chat_session.rs @@ -11,7 +11,7 @@ pub use op_editor_host_core::chat::ChatPoll; pub use op_editor_host_core::chat::{apply_poll_to_message, ChatSession}; use op_host_native::WidgetHostNative; -use op_web_daemon::chat_canvas_tools::execute_chat_tool; +use op_host_services::chat_canvas_tools::execute_chat_tool; // Turn launch + provider routing (split out at the 800-line cap). // `launch_if_pending` and friends live in the sibling file; the @@ -58,13 +58,13 @@ fn drain_tool_requests(state: &mut EditorState, session: &mut ChatSession) -> bo } let mut changed = false; for req in requests { - if req.name == op_web_daemon::chat_intent::APPLY_MODIFICATION_OP { + if req.name == op_host_services::chat_intent::APPLY_MODIFICATION_OP { let nodes = serde_json::from_str::(&req.args_json) .ok() .and_then(|v| v.get("nodes").and_then(|n| n.as_array().cloned())) .unwrap_or_default(); let (count, mutated) = - op_web_daemon::chat_canvas_tools::apply_design_modification(state, &nodes); + op_host_services::chat_canvas_tools::apply_design_modification(state, &nodes); if mutated { changed = true; } diff --git a/crates/op-host-desktop/src/chat_session_launch.rs b/crates/op-host-desktop/src/chat_session_launch.rs index 426338768..35c13cd1c 100644 --- a/crates/op-host-desktop/src/chat_session_launch.rs +++ b/crates/op-host-desktop/src/chat_session_launch.rs @@ -14,14 +14,14 @@ use op_host_native::WidgetHostNative; use op_orchestrator::{classify_intent, Intent}; use crate::chat_acp::AcpProvider; -use op_web_daemon::chat_builtin_http::ConfiguredBuiltinProvider; -use op_web_daemon::chat_canvas_tools::{chat_tool_channel, chat_tool_defs, ChatToolRequest}; -use op_web_daemon::chat_claude::ClaudeCodeProvider; -use op_web_daemon::chat_copilot::CopilotProvider; -use op_web_daemon::chat_http_server::OpenCodeProvider; -use op_web_daemon::chat_provider_llm::ChatProviderLlmClient; -use op_web_daemon::chat_subprocess::SubprocessProvider; -use op_web_daemon::chat_system_prompt::{ +use op_host_services::chat_builtin_http::ConfiguredBuiltinProvider; +use op_host_services::chat_canvas_tools::{chat_tool_channel, chat_tool_defs, ChatToolRequest}; +use op_host_services::chat_claude::ClaudeCodeProvider; +use op_host_services::chat_copilot::CopilotProvider; +use op_host_services::chat_http_server::OpenCodeProvider; +use op_host_services::chat_provider_llm::ChatProviderLlmClient; +use op_host_services::chat_subprocess::SubprocessProvider; +use op_host_services::chat_system_prompt::{ build_agent_system_prompt, build_chat_system_prompt, chat_history_from_transcript, }; use op_editor_host_core::design::DesignSession; @@ -91,10 +91,10 @@ pub fn launch_if_pending( host.mark_editor_state_dirty(); } let append_context = - op_web_daemon::chat_intent::detect_append_intent(host.editor_state(), &user_text); + op_host_services::chat_intent::detect_append_intent(host.editor_state(), &user_text); let initial_state = host.editor_state().clone(); let request = build_design_request(user_text, &initial_state, append_context); - *current_design = Some(op_web_daemon::design_session::start(llm, request, initial_state)); + *current_design = Some(op_host_services::design_session::start(llm, request, initial_state)); return true; } // Design intent but the selected agent has no ChatProvider @@ -248,8 +248,8 @@ fn launch_cli_standard_turn( DEFAULT_MAX_CHARS, ); let system_prompt = build_chat_system_prompt(state, user_text); - let modify_plan = op_web_daemon::chat_intent::build_modify_plan(state, user_text); - let append_context = op_web_daemon::chat_intent::detect_append_intent(state, user_text); + let modify_plan = op_host_services::chat_intent::build_modify_plan(state, user_text); + let append_context = op_host_services::chat_intent::detect_append_intent(state, user_text); let initial_state = state.clone(); let design_request = build_design_request(user_text.to_string(), &initial_state, append_context); @@ -292,7 +292,7 @@ fn launch_cli_standard_turn( indicator_epoch, )); - let plan = op_web_daemon::chat_intent::CliTurnPlan { + let plan = op_host_services::chat_intent::CliTurnPlan { user_text: user_text.to_string(), page_children_empty, classify_provider, @@ -308,7 +308,7 @@ fn launch_cli_standard_turn( thread::Builder::new() .name("op-chat-intent".into()) .spawn(move || { - op_web_daemon::chat_intent::run_cli_turn(plan, chat_tx, executor, delta_tx, cmd_tx); + op_host_services::chat_intent::run_cli_turn(plan, chat_tx, executor, delta_tx, cmd_tx); }) .expect("spawn op-chat-intent thread"); true @@ -330,8 +330,8 @@ pub fn drain_new_chat_request( // A fresh transcript must start a fresh provider conversation — // forget any resumable Claude Code / Copilot session so stale // context can't leak into the new chat. - op_web_daemon::chat_claude::reset_claude_chat_session(); - op_web_daemon::chat_copilot::reset_copilot_chat_session(); + op_host_services::chat_claude::reset_claude_chat_session(); + op_host_services::chat_copilot::reset_copilot_chat_session(); host.mark_editor_state_dirty(); true } diff --git a/crates/op-host-desktop/src/chat_session_tests.rs b/crates/op-host-desktop/src/chat_session_tests.rs index 0d927e4f7..a1a9fd9e4 100644 --- a/crates/op-host-desktop/src/chat_session_tests.rs +++ b/crates/op-host-desktop/src/chat_session_tests.rs @@ -1,5 +1,5 @@ use super::*; -use op_web_daemon::chat_system_prompt::{build_chat_system_prompt, chat_history_from_transcript}; +use op_host_services::chat_system_prompt::{build_chat_system_prompt, chat_history_from_transcript}; use op_ai::chat_history::{trim_chat_history, DEFAULT_MAX_CHARS, DEFAULT_MAX_MESSAGES}; use op_ai::chat_provider::{ChatDelta, ChatRequest, EchoProvider, StopReason}; use op_editor_core::{ChatMessage, ChatToolCall}; @@ -491,7 +491,7 @@ impl Iterator for ToolLoopIter { match self.step { 1 => Some(ChatDelta::ToolUse { name: self.tool.into(), - args: op_web_daemon::chat_agent_loop::tool_card_envelope("create", self.args), + args: op_host_services::chat_agent_loop::tool_card_envelope("create", self.args), }), 2 => { // Blocks until the UI thread (the test's pump loop) @@ -536,7 +536,7 @@ fn pump_executes_scripted_tool_call_against_live_state() { .push(ChatMessage::assistant_streaming()); let before = host.editor_state().active_children().len(); - let (executor, tool_rx) = op_web_daemon::chat_canvas_tools::chat_tool_channel(); + let (executor, tool_rx) = op_host_services::chat_canvas_tools::chat_tool_channel(); let provider = Box::new(ToolLoopProvider { executor: std::sync::Arc::new(executor), tool: "insert_node", diff --git a/crates/op-host-desktop/src/codegen_export.rs b/crates/op-host-desktop/src/codegen_export.rs index bc4117841..01b671740 100644 --- a/crates/op-host-desktop/src/codegen_export.rs +++ b/crates/op-host-desktop/src/codegen_export.rs @@ -116,7 +116,7 @@ fn build_live_structure_bundle( fn show_save_error(host: &WidgetHostNative, path: Option<&std::path::Path>, detail: &str) { crate::persistence::show_error_dialog_public( host, - op_web_daemon::doc_io::ErrorKind::Save, + op_host_services::doc_io::ErrorKind::Save, path, detail, ); diff --git a/crates/op-host-desktop/src/design_session.rs b/crates/op-host-desktop/src/design_session.rs index 991727a4b..8d319aedb 100644 --- a/crates/op-host-desktop/src/design_session.rs +++ b/crates/op-host-desktop/src/design_session.rs @@ -2,7 +2,7 @@ //! half of the design session. //! //! The worker spawn + viewport-fit math live in -//! [`op_web_daemon::design_session`]; this residual keeps the two UI-loop +//! [`op_host_services::design_session`]; this residual keeps the two UI-loop //! pumps (`pump_commands` / `pump_progress`, which take `&mut //! WidgetHostNative` — orphan rule) plus the progress-line renderer they //! fold into the chat transcript. @@ -19,7 +19,7 @@ pub use op_editor_host_core::design::DesignSession; use op_host_native::WidgetHostNative; use op_orchestrator::Progress; -use op_web_daemon::design_session::fit_design_viewport_to_content; +use op_host_services::design_session::fit_design_viewport_to_content; /// Drain every pending apply request from the in-flight design /// session and execute it against the real `EditorState`. Each diff --git a/crates/op-host-desktop/src/design_session_tests.rs b/crates/op-host-desktop/src/design_session_tests.rs index e28032b58..5203783df 100644 --- a/crates/op-host-desktop/src/design_session_tests.rs +++ b/crates/op-host-desktop/src/design_session_tests.rs @@ -1,5 +1,5 @@ use super::*; -use op_web_daemon::design_session::{ +use op_host_services::design_session::{ active_content_bounds, design_canvas_size, fit_design_viewport_to_content, }; use op_editor_core::{EditorCommand, EditorState}; diff --git a/crates/op-host-desktop/src/figma_import_session.rs b/crates/op-host-desktop/src/figma_import_session.rs index 158a5d94d..a323d7af9 100644 --- a/crates/op-host-desktop/src/figma_import_session.rs +++ b/crates/op-host-desktop/src/figma_import_session.rs @@ -25,7 +25,7 @@ use std::sync::mpsc::{self, Receiver, TryRecvError}; use std::thread; use crate::persistence::show_error_dialog_public; -use op_web_daemon::doc_io::ErrorKind; +use op_host_services::doc_io::ErrorKind; /// Successful worker output. Keep this Skia-free: building /// `LayoutScene` runs text measurement, which can contend with the diff --git a/crates/op-host-desktop/src/git_host.rs b/crates/op-host-desktop/src/git_host.rs index a5084c0cc..b31c0b4fa 100644 --- a/crates/op-host-desktop/src/git_host.rs +++ b/crates/op-host-desktop/src/git_host.rs @@ -207,7 +207,7 @@ impl DesktopApp { // can detect edits made *during* the async // pull — those the confirm above did not cover. self.git_pull_doc_baseline = - Some(op_web_daemon::doc_io::document_fingerprint(self.host.editor_state())); + Some(op_host_services::doc_io::document_fingerprint(self.host.editor_state())); self.host.editor_state_mut().editor_ui.git_panel.pulling = true; } } @@ -290,7 +290,7 @@ impl DesktopApp { } else if !message.is_empty() { match self.git_session.tracked_file().map(|p| p.to_path_buf()) { Some(path) => { - match op_web_daemon::doc_io::save_to_path(self.host.editor_state(), &path) { + match op_host_services::doc_io::save_to_path(self.host.editor_state(), &path) { Ok(()) => { self.mark_document_saved(); // Stage, then guard against an empty @@ -329,7 +329,7 @@ impl DesktopApp { } Err(detail) => persistence::show_error_dialog_public( &self.host, - op_web_daemon::doc_io::ErrorKind::Save, + op_host_services::doc_io::ErrorKind::Save, Some(&path), &detail, ), @@ -337,7 +337,7 @@ impl DesktopApp { } None => persistence::show_error_dialog_public( &self.host, - op_web_daemon::doc_io::ErrorKind::Save, + op_host_services::doc_io::ErrorKind::Save, None, "the open document is not tracked under this repository — \ save it into the repository folder first", diff --git a/crates/op-host-desktop/src/kit_persistence.rs b/crates/op-host-desktop/src/kit_persistence.rs index 3d4392ad4..490ac1935 100644 --- a/crates/op-host-desktop/src/kit_persistence.rs +++ b/crates/op-host-desktop/src/kit_persistence.rs @@ -146,7 +146,7 @@ fn kit_to_persisted(kit: &UIKit) -> PersistedKit { // --------------------------------------------------------------------------- /// Hydrate imported kits + the browser-open flag onto a fresh -/// `EditorState` (call once at startup, after `op_web_daemon::settings_io::load`). +/// `EditorState` (call once at startup, after `op_host_services::settings_io::load`). /// Best-effort: a missing / unreadable / malformed file leaves the /// built-in kits alone. pub fn load(state: &mut EditorState) { diff --git a/crates/op-host-desktop/src/main.rs b/crates/op-host-desktop/src/main.rs index 861e3b5a5..a6b226e2b 100644 --- a/crates/op-host-desktop/src/main.rs +++ b/crates/op-host-desktop/src/main.rs @@ -152,7 +152,7 @@ struct DesktopApp { /// Background AI-model discovery — probes the installed CLIs /// on a worker thread; its result is drained into /// `chat.available_models` on a later frame. - model_probe: op_web_daemon::model_discovery::ModelProbe, + model_probe: op_host_services::model_discovery::ModelProbe, /// Background auto-search jobs that replace generated empty image /// nodes with freely licensed remote images. image_search: image_search_session::ImageSearchSession, @@ -237,7 +237,7 @@ struct DesktopApp { /// external repository changes. last_git_refresh: Instant, /// Live in-process MCP HTTP server, started from Settings -> MCP. - mcp_server: Option, + mcp_server: Option, /// When set (via the `--live-mcp[=port]` launch flag used by /// `op start`), the editor force-enables the live MCP server on /// this port during `resumed()`, regardless of the persisted @@ -261,7 +261,7 @@ impl DesktopApp { let mut host = WidgetHostNative::new(); let fit_blank_frame = initial_file.is_none(); // Best-effort prefs restore onto the host's `EditorState`. - op_web_daemon::settings_io::load(host.editor_state_mut()); + op_host_services::settings_io::load(host.editor_state_mut()); // Imported UIKits + browser-open flag (`uikits.json`). Skipped // under test like the update / model probes — unit tests must // not see a developer machine's kit store. @@ -277,7 +277,7 @@ impl DesktopApp { host.mark_editor_state_dirty(); // Baseline for the unsaved-changes prompt — the fresh, // empty document is by definition "saved" (nothing to lose). - let saved_doc_fingerprint = op_web_daemon::doc_io::document_fingerprint(host.editor_state()); + let saved_doc_fingerprint = op_host_services::doc_io::document_fingerprint(host.editor_state()); let update_probe = if cfg!(test) { update_check::UpdateProbe::idle() } else { @@ -289,9 +289,9 @@ impl DesktopApp { ) }; let model_probe = if cfg!(test) { - op_web_daemon::model_discovery::ModelProbe::idle() + op_host_services::model_discovery::ModelProbe::idle() } else { - op_web_daemon::model_discovery::ModelProbe::spawn() + op_host_services::model_discovery::ModelProbe::spawn() }; Self { window: None, @@ -386,7 +386,7 @@ impl DesktopApp { // silent no-op when it finishes. figma_import_session::cancel(&mut self.host, &mut self.current_figma_import); self.image_search.reset(); - self.saved_doc_fingerprint = op_web_daemon::doc_io::document_fingerprint(self.host.editor_state()); + self.saved_doc_fingerprint = op_host_services::doc_io::document_fingerprint(self.host.editor_state()); self.rebind_git_session_for_current_path(); } @@ -475,7 +475,7 @@ impl DesktopApp { /// Whether the document carries edits since the last save / open /// / new. fn document_is_dirty(&self) -> bool { - op_web_daemon::doc_io::document_fingerprint(self.host.editor_state()) != self.saved_doc_fingerprint + op_host_services::doc_io::document_fingerprint(self.host.editor_state()) != self.saved_doc_fingerprint } /// Open documents macOS delivered through the open-documents @@ -490,8 +490,8 @@ impl DesktopApp { { let mut opened = false; for path in winit::platform::macos::drain_opened_file_urls() { - let is_op = op_web_daemon::doc_io::is_supported_document(&path); - let is_fig = op_web_daemon::doc_io::is_supported_figma_import(&path); + let is_op = op_host_services::doc_io::is_supported_document(&path); + let is_fig = op_host_services::doc_io::is_supported_figma_import(&path); if !is_op && !is_fig { continue; } @@ -552,8 +552,8 @@ impl DesktopApp { }; let mut opened = false; for path in paths { - let is_op = op_web_daemon::doc_io::is_supported_document(&path); - let is_fig = op_web_daemon::doc_io::is_supported_figma_import(&path); + let is_op = op_host_services::doc_io::is_supported_document(&path); + let is_fig = op_host_services::doc_io::is_supported_figma_import(&path); if (!is_op && !is_fig) || !path.is_file() { continue; } @@ -727,7 +727,7 @@ impl DesktopApp { // them. An unchanged document reloads silently. let edited_during_pull = baseline .map(|base| { - op_web_daemon::doc_io::document_fingerprint(self.host.editor_state()) != base + op_host_services::doc_io::document_fingerprint(self.host.editor_state()) != base }) .unwrap_or(false); if !edited_during_pull || self.confirm_document_reload() { @@ -857,7 +857,7 @@ impl DesktopApp { /// window is up (see `DesktopApp::apply_initial_file`). fn initial_file_from_argv() -> Option { std::env::args_os().skip(1).map(PathBuf::from).find(|p| { - (op_web_daemon::doc_io::is_supported_document(p) || op_web_daemon::doc_io::is_supported_figma_import(p)) + (op_host_services::doc_io::is_supported_document(p) || op_host_services::doc_io::is_supported_figma_import(p)) && p.is_file() }) } @@ -925,7 +925,7 @@ fn main() { // headless `--render-shots` rasterizer below, MCP — so they resolve // simple-icons instead of the unknown-glyph fallback dot. Set-once / // idempotent. - op_editor_ui::set_brand_catalog(op_web_daemon::web_static::ICONIFY_BRANDS_JSON); + op_editor_ui::set_brand_catalog(op_host_services::web_static::ICONIFY_BRANDS_JSON); // `--mcp` / `--mcp-http` swap the GUI for an MCP server mode; // when one of those ran, exit instead of opening a window. if mcp_serve::run_cli_if_requested() { @@ -985,10 +985,10 @@ fn main() { } } -// chat_intent moved to op_web_daemon::chat_intent (its headless tests +// chat_intent moved to op_host_services::chat_intent (its headless tests // moved alongside it as a `#[path]` sibling). Only the one host-coupled // test stayed here — it drives the GUI design-session pumps, which need -// `WidgetHostNative` (absent from op-web-daemon's default-features-off +// `WidgetHostNative` (absent from op-host-services's default-features-off // op-host-native dependency). #[cfg(test)] #[path = "chat_intent_host_tests.rs"] diff --git a/crates/op-host-desktop/src/main_tests.rs b/crates/op-host-desktop/src/main_tests.rs index 63edce895..220fdfb0c 100644 --- a/crates/op-host-desktop/src/main_tests.rs +++ b/crates/op-host-desktop/src/main_tests.rs @@ -2,7 +2,7 @@ //! `main.rs` to keep that file under the 800-line cap. use super::*; -use op_web_daemon::mcp_serve::tool_text; +use op_host_services::mcp_serve::tool_text; #[test] fn cursor_only_redraw_without_visible_state_change_skips_present() { @@ -184,7 +184,7 @@ fn live_mcp_http_server_applies_write_requests_to_editor_state() { use op_editor_core::PenNodeExt; - fn start_live_server() -> (op_web_daemon::mcp_live::McpLiveServer, u16) { + fn start_live_server() -> (op_host_services::mcp_live::McpLiveServer, u16) { // `bind(0)` to grab an ephemeral port, then re-`start` on that port, // has a TOCTOU window where the OS can reassign the port between the // probe-listener drop and the server bind — so a single attempt @@ -195,7 +195,7 @@ fn live_mcp_http_server_applies_write_requests_to_editor_state() { let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind ephemeral port"); listener.local_addr().expect("local addr").port() }; - if let Ok(server) = op_web_daemon::mcp_live::McpLiveServer::start(port) { + if let Ok(server) = op_host_services::mcp_live::McpLiveServer::start(port) { return (server, port); } } @@ -257,13 +257,13 @@ fn live_mcp_http_server_waits_for_split_http_request() { use std::sync::mpsc; use std::time::{Duration, Instant}; - fn start_live_server() -> (op_web_daemon::mcp_live::McpLiveServer, u16) { + fn start_live_server() -> (op_host_services::mcp_live::McpLiveServer, u16) { for _ in 0..20 { let port = { let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind ephemeral port"); listener.local_addr().expect("local addr").port() }; - if let Ok(server) = op_web_daemon::mcp_live::McpLiveServer::start(port) { + if let Ok(server) = op_host_services::mcp_live::McpLiveServer::start(port) { return (server, port); } } @@ -320,7 +320,7 @@ fn live_mcp_http_server_routes_file_path_requests_to_target_file() { use std::sync::mpsc; use std::time::{Duration, Instant}; - fn start_live_server() -> (op_web_daemon::mcp_live::McpLiveServer, u16) { + fn start_live_server() -> (op_host_services::mcp_live::McpLiveServer, u16) { // `bind(0)` to grab an ephemeral port, then re-`start` on that port, // has a TOCTOU window where the OS can reassign the port between the // probe-listener drop and the server bind — so a single attempt @@ -331,7 +331,7 @@ fn live_mcp_http_server_routes_file_path_requests_to_target_file() { let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind ephemeral port"); listener.local_addr().expect("local addr").port() }; - if let Ok(server) = op_web_daemon::mcp_live::McpLiveServer::start(port) { + if let Ok(server) = op_host_services::mcp_live::McpLiveServer::start(port) { return (server, port); } } @@ -420,13 +420,13 @@ fn live_mcp_http_server_replaces_document_via_rest_document_sync() { use std::sync::mpsc; use std::time::{Duration, Instant}; - fn start_live_server() -> (op_web_daemon::mcp_live::McpLiveServer, u16) { + fn start_live_server() -> (op_host_services::mcp_live::McpLiveServer, u16) { for _ in 0..20 { let port = { let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind ephemeral port"); listener.local_addr().expect("local addr").port() }; - if let Ok(server) = op_web_daemon::mcp_live::McpLiveServer::start(port) { + if let Ok(server) = op_host_services::mcp_live::McpLiveServer::start(port) { return (server, port); } } diff --git a/crates/op-host-desktop/src/mcp_runtime.rs b/crates/op-host-desktop/src/mcp_runtime.rs index ee0a2e902..d4735eb94 100644 --- a/crates/op-host-desktop/src/mcp_runtime.rs +++ b/crates/op-host-desktop/src/mcp_runtime.rs @@ -78,7 +78,7 @@ impl DesktopApp { if let Some(mut server) = self.mcp_server.take() { server.stop(); } - match op_web_daemon::mcp_live::McpLiveServer::start_with_wake(port, self.mcp_wake_callback()) { + match op_host_services::mcp_live::McpLiveServer::start_with_wake(port, self.mcp_wake_callback()) { Ok(server) => { let bound_port = server.port(); self.mcp_server = Some(server); @@ -88,7 +88,7 @@ impl DesktopApp { Err(err) => { eprintln!("openpencil-desktop mcp: failed to start on {port}: {err}"); if port != 0 { - match op_web_daemon::mcp_live::McpLiveServer::start_with_wake(0, self.mcp_wake_callback()) { + match op_host_services::mcp_live::McpLiveServer::start_with_wake(0, self.mcp_wake_callback()) { Ok(server) => { let bound_port = server.port(); eprintln!( diff --git a/crates/op-host-desktop/src/mcp_serve.rs b/crates/op-host-desktop/src/mcp_serve.rs index a9b6bc096..dd54a36c4 100644 --- a/crates/op-host-desktop/src/mcp_serve.rs +++ b/crates/op-host-desktop/src/mcp_serve.rs @@ -1,14 +1,14 @@ //! Argv dispatcher for the headless CLI server modes. //! //! The MCP stdio / HTTP server runners live in -//! [`op_web_daemon::mcp_serve`] and the headless web-canvas daemon in +//! [`op_host_services::mcp_serve`] and the headless web-canvas daemon in //! `crate::web_canvas_server`; this residual just inspects `argv` and //! routes `--mcp` / `--mcp-http` / `--serve-web` to them. `main` calls //! it before opening the GUI window. use std::path::PathBuf; -use op_web_daemon::mcp_serve::{run, run_http}; +use op_host_services::mcp_serve::{run, run_http}; /// If argv requests a headless server mode, run it and return `true` — /// the caller (`main`) should then exit. Returns `false` for normal @@ -59,12 +59,12 @@ pub fn run_cli_if_requested() -> bool { if first == "--serve-web" { // `--serve-web [doc] [--host ]`: doc optional (empty // document otherwise); `--host` opts in to a non-loopback bind. - let (port, path, host) = op_web_daemon::web_canvas_server::parse_serve_web_args(args) + let (port, path, host) = op_host_services::web_canvas_server::parse_serve_web_args(args) .unwrap_or_else(|e| { eprintln!("openpencil-desktop --serve-web: {e}"); std::process::exit(2); }); - if let Err(e) = op_web_daemon::web_canvas_server::run_web_canvas(path, port, &host) { + if let Err(e) = op_host_services::web_canvas_server::run_web_canvas(path, port, &host) { eprintln!("openpencil-desktop --serve-web: {e}"); std::process::exit(1); } diff --git a/crates/op-host-desktop/src/menu_action.rs b/crates/op-host-desktop/src/menu_action.rs index ca7af5e17..5585a92e8 100644 --- a/crates/op-host-desktop/src/menu_action.rs +++ b/crates/op-host-desktop/src/menu_action.rs @@ -25,7 +25,7 @@ impl DesktopApp { &mut self.host, &mut self.current_path, self.window.as_ref(), - ) == op_web_daemon::doc_io::ActionOutcome::Saved + ) == op_host_services::doc_io::ActionOutcome::Saved { self.mark_document_saved(); } diff --git a/crates/op-host-desktop/src/persistence.rs b/crates/op-host-desktop/src/persistence.rs index b63259112..ff28285d6 100644 --- a/crates/op-host-desktop/src/persistence.rs +++ b/crates/op-host-desktop/src/persistence.rs @@ -3,7 +3,7 @@ //! The headless load / save core (the canonical serializer, the //! `.opmeta` view-state sidecar, legacy-`DocPayload` detection, and the //! `EditorState` preference-carry helpers) lives in -//! [`op_web_daemon::doc_io`]; this module keeps the rfd / winit dialog +//! [`op_host_services::doc_io`]; this module keeps the rfd / winit dialog //! flow — the Save / Save-As / Open pickers, the file-menu //! [`run_action`] router, and the native error dialog — and imports the //! headless functions back. @@ -19,7 +19,7 @@ use std::path::PathBuf; use op_editor_core::EditorState; use op_host_native::WidgetHostNative; -use op_web_daemon::doc_io::{ +use op_host_services::doc_io::{ ActionOutcome, ErrorKind, active_page_bbox, load_editor_state, preserve_app_preferences, save_to_path, set_file_name_display, }; @@ -251,20 +251,20 @@ pub fn run_action( None } }; - let raster = |rf: op_web_daemon::export::RasterFormat| -> Result<(), String> { + let raster = |rf: op_host_services::export::RasterFormat| -> Result<(), String> { match &single_node { Some(id) => { - op_web_daemon::export::export_node_raster(scene, id, &path, rf, scale) + op_host_services::export::export_node_raster(scene, id, &path, rf, scale) } - None => op_web_daemon::export::export_raster(scene, &path, rf, scale), + None => op_host_services::export::export_raster(scene, &path, rf, scale), } }; let result: Result<(), String> = match fmt { - Fmt::Png => raster(op_web_daemon::export::RasterFormat::Png), - Fmt::Jpeg => raster(op_web_daemon::export::RasterFormat::Jpeg), - Fmt::Webp => raster(op_web_daemon::export::RasterFormat::Webp), - Fmt::Svg => op_web_daemon::export::export_svg(scene, &path), - Fmt::Pdf => op_web_daemon::export_pdf::export_pdf(scene, &path), + Fmt::Png => raster(op_host_services::export::RasterFormat::Png), + Fmt::Jpeg => raster(op_host_services::export::RasterFormat::Jpeg), + Fmt::Webp => raster(op_host_services::export::RasterFormat::Webp), + Fmt::Svg => op_host_services::export::export_svg(scene, &path), + Fmt::Pdf => op_host_services::export_pdf::export_pdf(scene, &path), }; if let Err(e) = result { eprintln!("[export-image] {e}"); @@ -398,7 +398,7 @@ mod tests { use super::*; // `save_to_path` arrives via `super::*`; `sidecar_path` is only // needed for test cleanup, so import it directly. - use op_web_daemon::doc_io::sidecar_path; + use op_host_services::doc_io::sidecar_path; /// A unique temp path under the OS temp dir for a round-trip test. fn temp_op_path(tag: &str) -> PathBuf { diff --git a/crates/op-host-desktop/src/provider_probe_host.rs b/crates/op-host-desktop/src/provider_probe_host.rs index 1b594b5e7..e4fda7cc3 100644 --- a/crates/op-host-desktop/src/provider_probe_host.rs +++ b/crates/op-host-desktop/src/provider_probe_host.rs @@ -4,24 +4,24 @@ //! pattern: the press handler raises //! `agent_settings.pending_provider_connect` (the request seam in //! op-editor-core), the redraw pump drains it onto a worker thread -//! running [`op_web_daemon::provider_probe::connect_provider`], and a later +//! running [`op_host_services::provider_probe::connect_provider`], and a later //! frame polls the outcome into `agent_settings` + //! `chat.discovered_models`. //! //! The job struct + outcome normalization live in -//! [`op_web_daemon::provider_probe_host`] (codex Issue 5 — the job is a +//! [`op_host_services::provider_probe_host`] (codex Issue 5 — the job is a //! `DesktopApp` field); this residual keeps only the `impl DesktopApp` //! pump, which drives the job through its public API. use op_editor_core::agent_settings::ProviderConnectOutcome; use crate::DesktopApp; -use op_web_daemon::model_discovery::model_entry_to_ec; -use op_web_daemon::provider_probe::ProbeOutcome; -use op_web_daemon::provider_probe_host::normalize_provider_probe_outcome; +use op_host_services::model_discovery::model_entry_to_ec; +use op_host_services::provider_probe::ProbeOutcome; +use op_host_services::provider_probe_host::normalize_provider_probe_outcome; // Re-export so `crate::provider_probe_host::ProviderConnectJob` (the // `DesktopApp` field type in `main.rs`) still resolves with zero churn. -pub use op_web_daemon::provider_probe_host::ProviderConnectJob; +pub use op_host_services::provider_probe_host::ProviderConnectJob; impl DesktopApp { /// Pump: poll a finished probe into editor state, then start the diff --git a/crates/op-host-desktop/src/render_cli.rs b/crates/op-host-desktop/src/render_cli.rs index 55a4c1349..91de4e654 100644 --- a/crates/op-host-desktop/src/render_cli.rs +++ b/crates/op-host-desktop/src/render_cli.rs @@ -4,13 +4,13 @@ //! loads a canonical `.op`, runs the SAME jian layout pass the editor //! canvas uses (`op_pen_loader::editor_state_to_layout_scene`), and //! writes one cropped PNG per top-level node on the active page via -//! [`op_web_daemon::export::export_node_raster`] — node-only screenshots (no +//! [`op_host_services::export::export_node_raster`] — node-only screenshots (no //! editor chrome) for the model-design benchmark. No GUI / GL window, //! so it runs headless in CI / scripts. use std::path::PathBuf; -use op_web_daemon::export::{export_node_raster, RasterFormat}; +use op_host_services::export::{export_node_raster, RasterFormat}; /// When `--render-shots` is present on argv, render node PNGs and return /// `true` so `main` exits 0; otherwise return `false` and let the normal diff --git a/crates/op-host-desktop/src/settings_io.rs b/crates/op-host-desktop/src/settings_io.rs index ef67aa19c..2215aa617 100644 --- a/crates/op-host-desktop/src/settings_io.rs +++ b/crates/op-host-desktop/src/settings_io.rs @@ -2,7 +2,7 @@ //! //! The headless settings persistence (load / save / fingerprint / //! save_if_changed + the on-disk payload DTOs + id allocators + locale -//! detection) lives in [`op_web_daemon::settings_io`]; this residual +//! detection) lives in [`op_host_services::settings_io`]; this residual //! keeps only `touch_recent`, which writes through the live //! `WidgetHostNative` (orphan rule — it takes the host type). diff --git a/crates/op-host-native/Cargo.toml b/crates/op-host-native/Cargo.toml index 5cae76664..39364f61b 100644 --- a/crates/op-host-native/Cargo.toml +++ b/crates/op-host-native/Cargo.toml @@ -14,7 +14,7 @@ path = "src/lib.rs" # `gl-host` (NON-default) enables the desktop winit/glutin interactive runner # tier + skia-safe's `gl` feature + accesskit. Consumers that need the GUI host # (`op-host-desktop`, `op-app`) opt in via `features = ["gl-host"]`. Left OFF — -# the default, used by `op-web-daemon` (via `default-features = false`) and by +# the default, used by `op-host-services` (via `default-features = false`) and by # the mobile `cargo check` (default features) — it yields a raster-only library: # no winit / glutin / jian-host-desktop / accesskit, and skia-safe WITHOUT `gl` # (so it emits no native GL link directives). The raster `NativeBackend` + diff --git a/crates/op-web-daemon/Cargo.toml b/crates/op-host-services/Cargo.toml similarity index 87% rename from crates/op-web-daemon/Cargo.toml rename to crates/op-host-services/Cargo.toml index 25dd1fe34..4bb717bfb 100644 --- a/crates/op-web-daemon/Cargo.toml +++ b/crates/op-host-services/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "op-web-daemon" +name = "op-host-services" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true -description = "OpenPencil headless web-canvas daemon — serves the wasm shell + REST/MCP, no winit/skia-GL" +description = "OpenPencil headless host services — the GUI-free backend (serve-web daemon, MCP, AI chat/orchestration, export, persistence) shared by op-host-desktop + op-host-web-server; no winit/skia-GL" [lib] -name = "op_web_daemon" +name = "op_host_services" path = "src/lib.rs" [features] @@ -64,7 +64,7 @@ zip = "2" glam = { version = "0.29", default-features = false, features = ["std"] } dirs = "5" # Raster + pdf skia for the export endpoint — NO `gl` (the headless server links -# no GL). Cargo keeps this gl-free in the isolated `-p op-web-daemon` build. +# no GL). Cargo keeps this gl-free in the isolated `-p op-host-services` build. skia-safe = { version = "0.97.0", default-features = false, features = [ "binary-cache", "textlayout", diff --git a/crates/op-web-daemon/src/acp_agent_probe_host.rs b/crates/op-host-services/src/acp_agent_probe_host.rs similarity index 100% rename from crates/op-web-daemon/src/acp_agent_probe_host.rs rename to crates/op-host-services/src/acp_agent_probe_host.rs diff --git a/crates/op-web-daemon/src/ai_proxy.rs b/crates/op-host-services/src/ai_proxy.rs similarity index 100% rename from crates/op-web-daemon/src/ai_proxy.rs rename to crates/op-host-services/src/ai_proxy.rs diff --git a/crates/op-web-daemon/src/chat_agent_loop.rs b/crates/op-host-services/src/chat_agent_loop.rs similarity index 100% rename from crates/op-web-daemon/src/chat_agent_loop.rs rename to crates/op-host-services/src/chat_agent_loop.rs diff --git a/crates/op-web-daemon/src/chat_agent_loop_tests.rs b/crates/op-host-services/src/chat_agent_loop_tests.rs similarity index 100% rename from crates/op-web-daemon/src/chat_agent_loop_tests.rs rename to crates/op-host-services/src/chat_agent_loop_tests.rs diff --git a/crates/op-web-daemon/src/chat_attachment.rs b/crates/op-host-services/src/chat_attachment.rs similarity index 100% rename from crates/op-web-daemon/src/chat_attachment.rs rename to crates/op-host-services/src/chat_attachment.rs diff --git a/crates/op-web-daemon/src/chat_builtin_http.rs b/crates/op-host-services/src/chat_builtin_http.rs similarity index 100% rename from crates/op-web-daemon/src/chat_builtin_http.rs rename to crates/op-host-services/src/chat_builtin_http.rs diff --git a/crates/op-web-daemon/src/chat_canvas_tools.rs b/crates/op-host-services/src/chat_canvas_tools.rs similarity index 100% rename from crates/op-web-daemon/src/chat_canvas_tools.rs rename to crates/op-host-services/src/chat_canvas_tools.rs diff --git a/crates/op-web-daemon/src/chat_claude.rs b/crates/op-host-services/src/chat_claude.rs similarity index 100% rename from crates/op-web-daemon/src/chat_claude.rs rename to crates/op-host-services/src/chat_claude.rs diff --git a/crates/op-web-daemon/src/chat_copilot.rs b/crates/op-host-services/src/chat_copilot.rs similarity index 100% rename from crates/op-web-daemon/src/chat_copilot.rs rename to crates/op-host-services/src/chat_copilot.rs diff --git a/crates/op-web-daemon/src/chat_http_server.rs b/crates/op-host-services/src/chat_http_server.rs similarity index 100% rename from crates/op-web-daemon/src/chat_http_server.rs rename to crates/op-host-services/src/chat_http_server.rs diff --git a/crates/op-web-daemon/src/chat_http_server_tests.rs b/crates/op-host-services/src/chat_http_server_tests.rs similarity index 100% rename from crates/op-web-daemon/src/chat_http_server_tests.rs rename to crates/op-host-services/src/chat_http_server_tests.rs diff --git a/crates/op-web-daemon/src/chat_intent.rs b/crates/op-host-services/src/chat_intent.rs similarity index 100% rename from crates/op-web-daemon/src/chat_intent.rs rename to crates/op-host-services/src/chat_intent.rs diff --git a/crates/op-web-daemon/src/chat_intent_tests.rs b/crates/op-host-services/src/chat_intent_tests.rs similarity index 100% rename from crates/op-web-daemon/src/chat_intent_tests.rs rename to crates/op-host-services/src/chat_intent_tests.rs diff --git a/crates/op-web-daemon/src/chat_provider_llm.rs b/crates/op-host-services/src/chat_provider_llm.rs similarity index 100% rename from crates/op-web-daemon/src/chat_provider_llm.rs rename to crates/op-host-services/src/chat_provider_llm.rs diff --git a/crates/op-web-daemon/src/chat_runtime.rs b/crates/op-host-services/src/chat_runtime.rs similarity index 100% rename from crates/op-web-daemon/src/chat_runtime.rs rename to crates/op-host-services/src/chat_runtime.rs diff --git a/crates/op-web-daemon/src/chat_spawn.rs b/crates/op-host-services/src/chat_spawn.rs similarity index 100% rename from crates/op-web-daemon/src/chat_spawn.rs rename to crates/op-host-services/src/chat_spawn.rs diff --git a/crates/op-web-daemon/src/chat_subprocess.rs b/crates/op-host-services/src/chat_subprocess.rs similarity index 100% rename from crates/op-web-daemon/src/chat_subprocess.rs rename to crates/op-host-services/src/chat_subprocess.rs diff --git a/crates/op-web-daemon/src/chat_subprocess_quirks.rs b/crates/op-host-services/src/chat_subprocess_quirks.rs similarity index 100% rename from crates/op-web-daemon/src/chat_subprocess_quirks.rs rename to crates/op-host-services/src/chat_subprocess_quirks.rs diff --git a/crates/op-web-daemon/src/chat_subprocess_tests.rs b/crates/op-host-services/src/chat_subprocess_tests.rs similarity index 100% rename from crates/op-web-daemon/src/chat_subprocess_tests.rs rename to crates/op-host-services/src/chat_subprocess_tests.rs diff --git a/crates/op-web-daemon/src/chat_system_prompt.rs b/crates/op-host-services/src/chat_system_prompt.rs similarity index 100% rename from crates/op-web-daemon/src/chat_system_prompt.rs rename to crates/op-host-services/src/chat_system_prompt.rs diff --git a/crates/op-web-daemon/src/design_session.rs b/crates/op-host-services/src/design_session.rs similarity index 100% rename from crates/op-web-daemon/src/design_session.rs rename to crates/op-host-services/src/design_session.rs diff --git a/crates/op-web-daemon/src/doc_io.rs b/crates/op-host-services/src/doc_io.rs similarity index 100% rename from crates/op-web-daemon/src/doc_io.rs rename to crates/op-host-services/src/doc_io.rs diff --git a/crates/op-web-daemon/src/export.rs b/crates/op-host-services/src/export.rs similarity index 100% rename from crates/op-web-daemon/src/export.rs rename to crates/op-host-services/src/export.rs diff --git a/crates/op-web-daemon/src/export/export_svg.rs b/crates/op-host-services/src/export/export_svg.rs similarity index 100% rename from crates/op-web-daemon/src/export/export_svg.rs rename to crates/op-host-services/src/export/export_svg.rs diff --git a/crates/op-web-daemon/src/export/scene_painter.rs b/crates/op-host-services/src/export/scene_painter.rs similarity index 100% rename from crates/op-web-daemon/src/export/scene_painter.rs rename to crates/op-host-services/src/export/scene_painter.rs diff --git a/crates/op-web-daemon/src/export/screenshot.rs b/crates/op-host-services/src/export/screenshot.rs similarity index 100% rename from crates/op-web-daemon/src/export/screenshot.rs rename to crates/op-host-services/src/export/screenshot.rs diff --git a/crates/op-web-daemon/src/export/tests.rs b/crates/op-host-services/src/export/tests.rs similarity index 100% rename from crates/op-web-daemon/src/export/tests.rs rename to crates/op-host-services/src/export/tests.rs diff --git a/crates/op-web-daemon/src/export_pdf.rs b/crates/op-host-services/src/export_pdf.rs similarity index 100% rename from crates/op-web-daemon/src/export_pdf.rs rename to crates/op-host-services/src/export_pdf.rs diff --git a/crates/op-host-services/src/lib.rs b/crates/op-host-services/src/lib.rs new file mode 100644 index 000000000..a29f2bf0c --- /dev/null +++ b/crates/op-host-services/src/lib.rs @@ -0,0 +1,51 @@ +//! OpenPencil headless host services — the GUI-free host backend. +//! +//! Everything the host does that doesn't need a window: the in-memory +//! `.op` document daemon (whole-document REST sync + SSE for the browser +//! `op-host-web` shell + external MCP/CLI clients), the stdio + HTTP MCP +//! servers, the AI chat providers + design orchestration, server-side +//! PNG/SVG/PDF export (skia raster), document/settings persistence, and +//! model/provider discovery. It links NO winit / glutin / muda / +//! accesskit adapters / skia-GL — the desktop GUI stack stays in +//! `op-host-native` / `op-host-desktop`. +//! +//! Both hosts depend on this crate: `op-host-desktop` embeds it for its +//! own `--serve-web` / MCP / chat / export, and the thin +//! `op-host-web-server` binary is just an argv dispatcher over it. +//! +//! Modules were migrated here out of `op-host-desktop` over Phases 2–5 of +//! the extraction (plan: +//! `openpencil-docs/superpowers/plans/2026-06-19-op-web-daemon-extraction.md`). + +// Migrated headless modules (Phases 2-5), kept alphabetical. +pub mod acp_agent_probe_host; +pub mod ai_proxy; +pub mod chat_agent_loop; +pub mod chat_attachment; +pub mod chat_builtin_http; +pub mod chat_canvas_tools; +pub mod chat_claude; +pub mod chat_copilot; +pub mod chat_http_server; +pub mod chat_intent; +pub mod chat_provider_llm; +pub mod chat_runtime; +pub mod chat_spawn; +pub mod chat_subprocess; +pub mod chat_subprocess_quirks; +pub mod chat_system_prompt; +pub mod design_session; +pub mod doc_io; +pub mod export; +pub mod export_pdf; +pub mod mcp_live; +pub mod mcp_serve; +pub mod model_discovery; +pub mod pre_validator; +pub mod provider_probe; +pub mod provider_probe_host; +pub mod provider_probe_models; +pub mod settings_io; +pub mod web_canvas_server; +pub mod web_chat_standard; +pub mod web_static; diff --git a/crates/op-web-daemon/src/mcp_live.rs b/crates/op-host-services/src/mcp_live.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_live.rs rename to crates/op-host-services/src/mcp_live.rs diff --git a/crates/op-web-daemon/src/mcp_live/screenshot.rs b/crates/op-host-services/src/mcp_live/screenshot.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_live/screenshot.rs rename to crates/op-host-services/src/mcp_live/screenshot.rs diff --git a/crates/op-web-daemon/src/mcp_serve.rs b/crates/op-host-services/src/mcp_serve.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_serve.rs rename to crates/op-host-services/src/mcp_serve.rs diff --git a/crates/op-web-daemon/src/mcp_serve/codegen_wire_tests.rs b/crates/op-host-services/src/mcp_serve/codegen_wire_tests.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_serve/codegen_wire_tests.rs rename to crates/op-host-services/src/mcp_serve/codegen_wire_tests.rs diff --git a/crates/op-web-daemon/src/mcp_serve/doc_sync.rs b/crates/op-host-services/src/mcp_serve/doc_sync.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_serve/doc_sync.rs rename to crates/op-host-services/src/mcp_serve/doc_sync.rs diff --git a/crates/op-web-daemon/src/mcp_serve/file_path.rs b/crates/op-host-services/src/mcp_serve/file_path.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_serve/file_path.rs rename to crates/op-host-services/src/mcp_serve/file_path.rs diff --git a/crates/op-web-daemon/src/mcp_serve/schemas.rs b/crates/op-host-services/src/mcp_serve/schemas.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_serve/schemas.rs rename to crates/op-host-services/src/mcp_serve/schemas.rs diff --git a/crates/op-web-daemon/src/mcp_serve/tests.rs b/crates/op-host-services/src/mcp_serve/tests.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_serve/tests.rs rename to crates/op-host-services/src/mcp_serve/tests.rs diff --git a/crates/op-web-daemon/src/mcp_serve/wire.rs b/crates/op-host-services/src/mcp_serve/wire.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_serve/wire.rs rename to crates/op-host-services/src/mcp_serve/wire.rs diff --git a/crates/op-web-daemon/src/mcp_serve/wire_tests.rs b/crates/op-host-services/src/mcp_serve/wire_tests.rs similarity index 100% rename from crates/op-web-daemon/src/mcp_serve/wire_tests.rs rename to crates/op-host-services/src/mcp_serve/wire_tests.rs diff --git a/crates/op-web-daemon/src/model_discovery.rs b/crates/op-host-services/src/model_discovery.rs similarity index 100% rename from crates/op-web-daemon/src/model_discovery.rs rename to crates/op-host-services/src/model_discovery.rs diff --git a/crates/op-web-daemon/src/pre_validator.rs b/crates/op-host-services/src/pre_validator.rs similarity index 100% rename from crates/op-web-daemon/src/pre_validator.rs rename to crates/op-host-services/src/pre_validator.rs diff --git a/crates/op-web-daemon/src/provider_probe.rs b/crates/op-host-services/src/provider_probe.rs similarity index 100% rename from crates/op-web-daemon/src/provider_probe.rs rename to crates/op-host-services/src/provider_probe.rs diff --git a/crates/op-web-daemon/src/provider_probe_host.rs b/crates/op-host-services/src/provider_probe_host.rs similarity index 100% rename from crates/op-web-daemon/src/provider_probe_host.rs rename to crates/op-host-services/src/provider_probe_host.rs diff --git a/crates/op-web-daemon/src/provider_probe_models.rs b/crates/op-host-services/src/provider_probe_models.rs similarity index 100% rename from crates/op-web-daemon/src/provider_probe_models.rs rename to crates/op-host-services/src/provider_probe_models.rs diff --git a/crates/op-web-daemon/src/provider_probe_tests.rs b/crates/op-host-services/src/provider_probe_tests.rs similarity index 100% rename from crates/op-web-daemon/src/provider_probe_tests.rs rename to crates/op-host-services/src/provider_probe_tests.rs diff --git a/crates/op-web-daemon/src/settings_io.rs b/crates/op-host-services/src/settings_io.rs similarity index 100% rename from crates/op-web-daemon/src/settings_io.rs rename to crates/op-host-services/src/settings_io.rs diff --git a/crates/op-web-daemon/src/settings_io_tests.rs b/crates/op-host-services/src/settings_io_tests.rs similarity index 100% rename from crates/op-web-daemon/src/settings_io_tests.rs rename to crates/op-host-services/src/settings_io_tests.rs diff --git a/crates/op-web-daemon/src/web_canvas_server.rs b/crates/op-host-services/src/web_canvas_server.rs similarity index 100% rename from crates/op-web-daemon/src/web_canvas_server.rs rename to crates/op-host-services/src/web_canvas_server.rs diff --git a/crates/op-web-daemon/src/web_canvas_server_tests.rs b/crates/op-host-services/src/web_canvas_server_tests.rs similarity index 100% rename from crates/op-web-daemon/src/web_canvas_server_tests.rs rename to crates/op-host-services/src/web_canvas_server_tests.rs diff --git a/crates/op-web-daemon/src/web_chat_standard.rs b/crates/op-host-services/src/web_chat_standard.rs similarity index 100% rename from crates/op-web-daemon/src/web_chat_standard.rs rename to crates/op-host-services/src/web_chat_standard.rs diff --git a/crates/op-web-daemon/src/web_static.rs b/crates/op-host-services/src/web_static.rs similarity index 100% rename from crates/op-web-daemon/src/web_static.rs rename to crates/op-host-services/src/web_static.rs diff --git a/crates/op-web-daemon/src/web_static/index.html b/crates/op-host-services/src/web_static/index.html similarity index 100% rename from crates/op-web-daemon/src/web_static/index.html rename to crates/op-host-services/src/web_static/index.html diff --git a/crates/op-web-daemon/src/web_static/missing_bundle.html b/crates/op-host-services/src/web_static/missing_bundle.html similarity index 100% rename from crates/op-web-daemon/src/web_static/missing_bundle.html rename to crates/op-host-services/src/web_static/missing_bundle.html diff --git a/crates/op-host-web-server/Cargo.toml b/crates/op-host-web-server/Cargo.toml index 92c75d1a0..d4c5b777b 100644 --- a/crates/op-host-web-server/Cargo.toml +++ b/crates/op-host-web-server/Cargo.toml @@ -4,11 +4,11 @@ version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true -description = "OpenPencil headless web/MCP server binary — links op-web-daemon only, no winit/skia-GL" +description = "OpenPencil headless web/MCP server binary — links op-host-services only, no winit/skia-GL" [[bin]] name = "op-host-web-server" path = "src/main.rs" [dependencies] -op-web-daemon = { path = "../op-web-daemon" } +op-host-services = { path = "../op-host-services" } diff --git a/crates/op-host-web-server/src/main.rs b/crates/op-host-web-server/src/main.rs index 8a2b40aa9..8bb8593e0 100644 --- a/crates/op-host-web-server/src/main.rs +++ b/crates/op-host-web-server/src/main.rs @@ -1,6 +1,6 @@ //! Headless OpenPencil web / MCP server binary. //! -//! Links only `op-web-daemon` (the extracted headless daemon) — no +//! Links only `op-host-services` (the extracted headless daemon) — no //! winit / glutin / muda / accesskit adapters / skia-GL — so the //! container / server image stays GUI-free. It mirrors the `--serve-web` //! / `--mcp` / `--mcp-http` argv arms the desktop binary's @@ -27,12 +27,12 @@ fn main() { // `--serve-web [doc] [--host ]`: doc optional // (empty document otherwise); `--host` opts in to a // non-loopback bind. - let (port, path, host) = op_web_daemon::web_canvas_server::parse_serve_web_args(args) + let (port, path, host) = op_host_services::web_canvas_server::parse_serve_web_args(args) .unwrap_or_else(|e| { eprintln!("op-host-web-server --serve-web: {e}"); exit(2); }); - if let Err(e) = op_web_daemon::web_canvas_server::run_web_canvas(path, port, &host) { + if let Err(e) = op_host_services::web_canvas_server::run_web_canvas(path, port, &host) { eprintln!("op-host-web-server --serve-web: {e}"); exit(1); } @@ -42,7 +42,7 @@ fn main() { eprintln!("op-host-web-server --mcp: missing arg"); exit(2); }; - if let Err(e) = op_web_daemon::mcp_serve::run(PathBuf::from(path)) { + if let Err(e) = op_host_services::mcp_serve::run(PathBuf::from(path)) { eprintln!("op-host-web-server --mcp: {e}"); exit(1); } @@ -62,7 +62,7 @@ fn main() { eprintln!("op-host-web-server --mcp-http: missing arg"); exit(2); }; - if let Err(e) = op_web_daemon::mcp_serve::run_http(PathBuf::from(path), port) { + if let Err(e) = op_host_services::mcp_serve::run_http(PathBuf::from(path), port) { eprintln!("op-host-web-server --mcp-http: {e}"); exit(1); } diff --git a/crates/op-web-daemon/src/lib.rs b/crates/op-web-daemon/src/lib.rs deleted file mode 100644 index 2ba1df80b..000000000 --- a/crates/op-web-daemon/src/lib.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! OpenPencil headless web-canvas daemon. -//! -//! The Rust analog of the TS web app's Nitro routes: it owns the canonical -//! `.op` document in memory and serves it (whole-document REST sync + SSE) to -//! the browser WASM shell (`op-host-web`) and to external MCP/CLI clients, plus -//! server-side PNG/screenshot export via skia raster. It links NO winit / -//! glutin / muda / accesskit / skia-GL — the desktop GUI stack stays in -//! `op-host-desktop`. -//! -//! Modules are migrated here from `op-host-desktop` over Phases 2-5 of the -//! op-web-daemon extraction (see -//! `openpencil-docs/superpowers/plans/2026-06-19-op-web-daemon-extraction.md`). -//! Both `op-host-desktop` (for its `--serve-web` mode) and a thin -//! `op-host-web-server` binary depend on this crate. - -// Migrated headless modules (Phases 2-5), kept alphabetical. -pub mod acp_agent_probe_host; -pub mod ai_proxy; -pub mod chat_agent_loop; -pub mod chat_attachment; -pub mod chat_builtin_http; -pub mod chat_canvas_tools; -pub mod chat_claude; -pub mod chat_copilot; -pub mod chat_http_server; -pub mod chat_intent; -pub mod chat_provider_llm; -pub mod chat_runtime; -pub mod chat_spawn; -pub mod chat_subprocess; -pub mod chat_subprocess_quirks; -pub mod chat_system_prompt; -pub mod design_session; -pub mod doc_io; -pub mod export; -pub mod export_pdf; -pub mod mcp_live; -pub mod mcp_serve; -pub mod model_discovery; -pub mod pre_validator; -pub mod provider_probe; -pub mod provider_probe_host; -pub mod provider_probe_models; -pub mod settings_io; -pub mod web_canvas_server; -pub mod web_chat_standard; -pub mod web_static; diff --git a/tools/check-web-server-headless.sh b/tools/check-web-server-headless.sh index 52783f8d1..10f1e6675 100755 --- a/tools/check-web-server-headless.sh +++ b/tools/check-web-server-headless.sh @@ -2,7 +2,7 @@ # Headless-boundary guard for the web / MCP server binary. # # The whole point of `op-host-web-server` (Approach Y) is that it links the -# extracted `op-web-daemon` and NOTHING from the desktop GUI stack: no winit / +# extracted `op-host-services` and NOTHING from the desktop GUI stack: no winit / # glutin / casement (windowing), no muda (native menus), no accesskit platform # ADAPTERS (a11y bridges), and skia-safe WITHOUT the `gl` feature (raster only). # This guard fails the build if any of those leak into the isolated dep graph,