refactor(host): rename op-web-daemon crate to op-host-services
The extracted headless crate is consumed by BOTH op-host-desktop (the GUI binary, for its embedded --serve-web/MCP/chat/export) AND op-host-web-server — so 'web-daemon' was misleading. Renamed crate dir + package + lib (op_web_daemon -> op_host_services) across all consumer files (114 refs in 24 files) + the 4 Cargo.toml deps + Dockerfile/guard comments; identity docs rewritten (it's the GUI-free host backend — daemon/MCP/AI/export/persistence — not web-specific). No behavior change. (Lock renamed accordingly; the concurrent actor's op-host-web serde line excluded.)
This commit is contained in:
parent
b7951fc598
commit
88a571c028
72
Cargo.lock
generated
72
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ impl ApplicationHandler<DesktopEvent> 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<DesktopEvent> 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<DesktopEvent> 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<DesktopEvent> 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<DesktopEvent> 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<DesktopEvent> 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<DesktopEvent> 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<DesktopEvent> 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<DesktopEvent> 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<DesktopEvent> 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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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::<serde_json::Value>(&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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<op_web_daemon::mcp_live::McpLiveServer>,
|
||||
mcp_server: Option<op_host_services::mcp_live::McpLiveServer>,
|
||||
/// 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<PathBuf> {
|
||||
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"]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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 <port> [doc] [--host <addr>]`: 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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` +
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
51
crates/op-host-services/src/lib.rs
Normal file
51
crates/op-host-services/src/lib.rs
Normal file
|
|
@ -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;
|
||||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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 <port> [doc] [--host <addr>]`: 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 <path> 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 <path> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue