You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/22 16:48:08 UTC

(maka) branch feat/runtime-host-rust updated: fix(plugins): restore ordinary roots from durable creation provenance

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch feat/runtime-host-rust
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/feat/runtime-host-rust by this push:
     new a4d71c8944 fix(plugins): restore ordinary roots from durable creation provenance
a4d71c8944 is described below

commit a4d71c89444dd2f9edb642732a59225f39673944
Author: Wang <m4...@gmail.com>
AuthorDate: Wed Sep 23 00:48:00 2026 +0800

    fix(plugins): restore ordinary roots from durable creation provenance
    
    Generated-by: Codex
---
 crates/event-log/migrations/0001_runtime.sql       |  5 +-
 crates/event-log/src/sessions.rs                   | 37 ++++++-------
 .../src/sessions/{manager.rs => origin.rs}         | 23 ++++++--
 .../{session_manager.rs => plugin_sessions.rs}     | 61 +++++++++++++++++-----
 crates/plugins/src/execution.rs                    |  2 +-
 crates/runtime-host/src/execution/plugins.rs       |  2 +-
 crates/runtime-host/src/execution/plugins/root.rs  | 48 +++++++++--------
 .../tests/fixtures/background-plugin.mjs           | 21 ++++++++
 .../tests/integration/plugin_commands/boundary.rs  | 49 ++++++++++++++++-
 crates/workhub/src/assignment.rs                   | 16 +++++-
 packages/plugin-sdk/README.md                      |  2 +-
 packages/plugin-sdk/README.zh-CN.md                |  2 +-
 packages/plugin-sdk/src/execution.ts               |  2 +-
 13 files changed, 201 insertions(+), 69 deletions(-)

diff --git a/crates/event-log/migrations/0001_runtime.sql b/crates/event-log/migrations/0001_runtime.sql
index e264dc1df4..c7cb20ee86 100644
--- a/crates/event-log/migrations/0001_runtime.sql
+++ b/crates/event-log/migrations/0001_runtime.sql
@@ -301,11 +301,12 @@ CREATE TABLE model_request_compositions (
 
 CREATE INDEX model_request_composition_digest ON model_request_compositions(digest);
 
-CREATE TABLE session_managers (
+CREATE TABLE plugin_sessions (
     session_id TEXT PRIMARY KEY NOT NULL,
     package_id TEXT NOT NULL,
     scope_id TEXT NOT NULL,
-    fingerprint TEXT NOT NULL
+    fingerprint TEXT NOT NULL,
+    managed INTEGER NOT NULL CHECK(managed IN (0, 1))
 );
 
 CREATE TABLE host_effects (
diff --git a/crates/event-log/src/sessions.rs b/crates/event-log/src/sessions.rs
index 1bf526179d..ac33a12cc3 100644
--- a/crates/event-log/src/sessions.rs
+++ b/crates/event-log/src/sessions.rs
@@ -21,9 +21,9 @@
 
 mod activity;
 mod execution;
-mod manager;
 mod metadata;
-pub use manager::ManagedSession;
+mod origin;
+pub use origin::PluginSession;
 pub(crate) mod read_state;
 pub(crate) use activity::{initialize_execution, project_execution, register_functions};
 pub(crate) use execution::advance_revision;
@@ -106,23 +106,23 @@ impl EventLog {
             .await
     }
 
-    /// Manager ownership and Session creation share the same transaction.
+    /// Creator identity, optional management and Session creation share one transaction.
     /// Only Host admission supplies the bound package/scope.
-    pub async fn create_managed_session<T: Serialize + DeserializeOwned + Send + 'static>(
+    pub async fn create_plugin_session<T: Serialize + DeserializeOwned + Send + 'static>(
         &self,
-        claim: &ManagedSession,
+        claim: &PluginSession,
         configuration: &T,
         now: u64,
     ) -> Result<SessionRecord<T>, StoreError> {
-        if claim.manager.scope() == &maka_plugins::composition::Scope::DesktopUi {
-            return Err(invalid("Desktop UI cannot manage Host Sessions"));
+        if claim.creator.scope() == &maka_plugins::composition::Scope::DesktopUi {
+            return Err(invalid("Desktop UI cannot create Host Sessions"));
         }
         self.create_session_owned(
             &claim.session_id,
             &claim.fingerprint,
             configuration,
             now,
-            Some(claim.manager.clone()),
+            Some((claim.creator.clone(), claim.managed)),
         )
         .await
     }
@@ -133,7 +133,7 @@ impl EventLog {
         fingerprint: &str,
         configuration: &T,
         now: u64,
-        manager: Option<maka_plugins::storage::Namespace>,
+        origin: Option<(maka_plugins::storage::Namespace, bool)>,
     ) -> Result<SessionRecord<T>, StoreError> {
         self.validate_root()?;
         validate_id(id)?;
@@ -148,23 +148,24 @@ impl EventLog {
                 Box::pin(async move {
                     let mut tx = connection.begin_with("BEGIN IMMEDIATE").await?;
                     if let Some(record) = probe(&mut tx, &id, &fingerprint).await? {
-                        let stored: Option<(String, String)> = sqlx::query_as(
-                            "SELECT package_id, scope_id FROM session_managers WHERE session_id = ?",
+                        let stored: Option<(String, String, bool)> = sqlx::query_as(
+                            "SELECT package_id, scope_id, managed FROM plugin_sessions WHERE session_id = ?",
                         ).bind(&id).fetch_optional(&mut *tx).await?;
-                        let requested = manager.as_ref().map(|owner| (
-                            owner.package().to_owned(), String::from(owner.scope().clone()),
+                        let requested = origin.as_ref().map(|(owner, managed)| (
+                            owner.package().to_owned(), String::from(owner.scope().clone()), *managed,
                         ));
                         if stored != requested {
                             return Err(StoreError::SessionConflict);
                         }
                         return Ok(record);
                     }
-                    if let Some(manager) = manager {
-                        sqlx::query("INSERT INTO session_managers VALUES (?, ?, ?, ?)")
+                    if let Some((creator, managed)) = origin {
+                        sqlx::query("INSERT INTO plugin_sessions VALUES (?, ?, ?, ?, ?)")
                             .bind(&id)
-                            .bind(manager.package())
-                            .bind(String::from(manager.scope().clone()))
+                            .bind(creator.package())
+                            .bind(String::from(creator.scope().clone()))
                             .bind(&fingerprint)
+                            .bind(managed)
                             .execute(&mut *tx)
                             .await?;
                     }
@@ -329,7 +330,7 @@ pub(crate) async fn insert(
         return Err(invalid("session configuration exceeds 64 KiB"));
     }
     let reserved: Option<String> =
-        sqlx::query_scalar("SELECT fingerprint FROM session_managers WHERE session_id = ?")
+        sqlx::query_scalar("SELECT fingerprint FROM plugin_sessions WHERE session_id = ?")
             .bind(id)
             .fetch_optional(&mut *tx)
             .await?;
diff --git a/crates/event-log/src/sessions/manager.rs b/crates/event-log/src/sessions/origin.rs
similarity index 75%
rename from crates/event-log/src/sessions/manager.rs
rename to crates/event-log/src/sessions/origin.rs
index 369fd68c42..cededb8176 100644
--- a/crates/event-log/src/sessions/manager.rs
+++ b/crates/event-log/src/sessions/origin.rs
@@ -24,15 +24,28 @@ use sqlx::Row;
 
 /// Host-bound creation identity, not a caller-provided authorization credential.
 #[derive(Clone, Debug)]
-pub struct ManagedSession {
+pub struct PluginSession {
     pub session_id: String,
-    pub manager: Namespace,
-    /// Stable identity of the managed creation request.
+    pub creator: Namespace,
+    /// Stable identity of the original creation request.
     pub fingerprint: String,
+    pub managed: bool,
 }
 
 impl EventLog {
     pub async fn session_manager(&self, session_id: &str) -> Result<Option<Namespace>, StoreError> {
+        self.session_plugin(session_id, true).await
+    }
+
+    pub async fn session_creator(&self, session_id: &str) -> Result<Option<Namespace>, StoreError> {
+        self.session_plugin(session_id, false).await
+    }
+
+    async fn session_plugin(
+        &self,
+        session_id: &str,
+        managed_only: bool,
+    ) -> Result<Option<Namespace>, StoreError> {
         self.validate_root()?;
         validate_id(session_id)?;
         let session_id = session_id.to_owned();
@@ -40,9 +53,11 @@ impl EventLog {
             .run(move |connection| {
                 Box::pin(async move {
                     sqlx::query(
-                        "SELECT package_id, scope_id FROM session_managers WHERE session_id = ?",
+                        "SELECT package_id, scope_id FROM plugin_sessions
+                         WHERE session_id = ? AND (NOT ? OR managed = 1)",
                     )
                     .bind(session_id)
+                    .bind(managed_only)
                     .fetch_optional(connection)
                     .await?
                     .map(|row| {
diff --git a/crates/event-log/tests/session_manager.rs b/crates/event-log/tests/plugin_sessions.rs
similarity index 59%
rename from crates/event-log/tests/session_manager.rs
rename to crates/event-log/tests/plugin_sessions.rs
index e4735a0a19..eed42cf6ac 100644
--- a/crates/event-log/tests/session_manager.rs
+++ b/crates/event-log/tests/plugin_sessions.rs
@@ -17,27 +17,28 @@
  * under the License.
  */
 
-use maka_event_log::{EventLog, StoreError, sessions::ManagedSession};
+use maka_event_log::{EventLog, StoreError, sessions::PluginSession};
 use maka_plugins::{composition::Scope, storage::Namespace};
 use serde_json::{Value, json};
 
 #[tokio::test]
-async fn managed_creation_is_atomic_and_cannot_adopt_or_reassign_existing_sessions() {
+async fn plugin_creation_is_atomic_and_cannot_adopt_or_reassign_existing_sessions() {
     let directory = tempfile::tempdir().unwrap();
     let path = directory.path().join("events.sqlite");
-    let claim = ManagedSession {
+    let claim = PluginSession {
         session_id: "example-inbox".into(),
-        manager: Namespace::new("example.workflow", Scope::Profile).unwrap(),
+        creator: Namespace::new("example.workflow", Scope::Profile).unwrap(),
         fingerprint: "owned-creation".into(),
+        managed: true,
     };
     let log = EventLog::open(&path).await.unwrap();
     let configuration = json!({"name":"inbox"});
     let record = log
-        .create_managed_session(&claim, &configuration, 1)
+        .create_plugin_session(&claim, &configuration, 1)
         .await
         .unwrap();
     assert_eq!(
-        log.create_managed_session(&claim, &configuration, 2)
+        log.create_plugin_session(&claim, &configuration, 2)
             .await
             .unwrap(),
         record
@@ -47,20 +48,20 @@ async fn managed_creation_is_atomic_and_cannot_adopt_or_reassign_existing_sessio
             .await,
         Err(StoreError::SessionConflict)
     ));
-    for manager in [
+    for creator in [
         Namespace::new("other.workflow", Scope::Profile).unwrap(),
         Namespace::new("example.workflow", Scope::Session("other".into())).unwrap(),
     ] {
-        let wrong = ManagedSession {
-            manager,
+        let wrong = PluginSession {
+            creator,
             ..claim.clone()
         };
         assert!(matches!(
-            log.create_managed_session(&wrong, &configuration, 2).await,
+            log.create_plugin_session(&wrong, &configuration, 2).await,
             Err(StoreError::SessionConflict)
         ));
     }
-    let ordinary = ManagedSession {
+    let ordinary = PluginSession {
         session_id: "ordinary".into(),
         ..claim.clone()
     };
@@ -68,16 +69,50 @@ async fn managed_creation_is_atomic_and_cannot_adopt_or_reassign_existing_sessio
         .await
         .unwrap();
     assert!(matches!(
-        log.create_managed_session(&ordinary, &configuration, 2)
+        log.create_plugin_session(&ordinary, &configuration, 2)
             .await,
         Err(StoreError::SessionConflict)
     ));
     assert_eq!(log.session_manager("ordinary").await.unwrap(), None);
+    assert_eq!(log.session_creator("ordinary").await.unwrap(), None);
+    let delegated = PluginSession {
+        session_id: "delegated".into(),
+        managed: false,
+        ..claim.clone()
+    };
+    log.create_plugin_session(&delegated, &configuration, 2)
+        .await
+        .unwrap();
+    for conflicting in [
+        PluginSession {
+            managed: true,
+            ..delegated.clone()
+        },
+        PluginSession {
+            fingerprint: "changed".into(),
+            ..delegated.clone()
+        },
+        PluginSession {
+            creator: Namespace::new("other", Scope::Profile).unwrap(),
+            ..delegated.clone()
+        },
+    ] {
+        assert!(matches!(
+            log.create_plugin_session(&conflicting, &configuration, 3)
+                .await,
+            Err(StoreError::SessionConflict)
+        ));
+    }
     log.close().await.unwrap();
     let log = EventLog::open(&path).await.unwrap();
     assert_eq!(
         log.session_manager(&claim.session_id).await.unwrap(),
-        Some(claim.manager)
+        Some(claim.creator.clone())
+    );
+    assert_eq!(log.session_manager("delegated").await.unwrap(), None);
+    assert_eq!(
+        log.session_creator("delegated").await.unwrap(),
+        Some(claim.creator)
     );
     assert_eq!(
         log.get_session::<Value>(&claim.session_id).await.unwrap(),
diff --git a/crates/plugins/src/execution.rs b/crates/plugins/src/execution.rs
index fecd041a41..78b164c9b1 100644
--- a/crates/plugins/src/execution.rs
+++ b/crates/plugins/src/execution.rs
@@ -510,7 +510,7 @@ pub trait Commands: Send + Sync + std::any::Any {
         &self,
         request: CreateRoot,
     ) -> futures_util::future::BoxFuture<'_, Result<ChildSession, CommandError>>;
-    /// Recover this namespace's managed root without replaying its creation settings.
+    /// Recover a root created by this namespace without replaying its creation settings.
     /// Rechecks the current workspace and source ceilings. Absence is only an
     /// observation: a concurrent creation may still commit with this operation ID.
     fn restore_root(
diff --git a/crates/runtime-host/src/execution/plugins.rs b/crates/runtime-host/src/execution/plugins.rs
index 39d2a89622..6b20164d82 100644
--- a/crates/runtime-host/src/execution/plugins.rs
+++ b/crates/runtime-host/src/execution/plugins.rs
@@ -690,7 +690,7 @@ impl Commands for BoundCommands {
         &self,
         operation_id: String,
     ) -> BoxFuture<'_, Result<Option<ChildSession>, Error>> {
-        Box::pin(self.restore_managed_root(operation_id))
+        Box::pin(self.restore_created_root(operation_id))
     }
     fn boundaries(&self) -> Result<Vec<maka_plugins::execution::SessionBoundary>, Error> {
         let _lease = self.context.resource_call().map_err(|_| Error::Revoked)?;
diff --git a/crates/runtime-host/src/execution/plugins/root.rs b/crates/runtime-host/src/execution/plugins/root.rs
index 5484630746..7d8600a971 100644
--- a/crates/runtime-host/src/execution/plugins/root.rs
+++ b/crates/runtime-host/src/execution/plugins/root.rs
@@ -78,7 +78,7 @@ impl BoundCommands {
         Ok(format!("plugin-root-{:x}", Sha256::digest(bytes)))
     }
 
-    pub(super) async fn restore_managed_root(
+    pub(super) async fn restore_created_root(
         &self,
         operation_id: String,
     ) -> Result<Option<ChildSession>, Error> {
@@ -107,7 +107,7 @@ impl BoundCommands {
         };
         if host
             .log
-            .session_manager(&id)
+            .session_creator(&id)
             .await
             .map_err(storage)?
             .as_ref()
@@ -299,26 +299,20 @@ impl BoundCommands {
                         if submission_stop.is_cancelled() {
                             return Err(Error::Revoked);
                         }
-                        let record = if request.managed {
-                            worker
-                                .log
-                                .create_managed_session(
-                                    &maka_event_log::sessions::ManagedSession {
-                                        session_id: id.clone(),
-                                        manager: namespace.clone(),
-                                        fingerprint: fingerprint.clone(),
-                                    },
-                                    &expected,
-                                    now()?,
-                                )
-                                .await
-                        } else {
-                            worker
-                                .log
-                                .create_session(&id, &fingerprint, &expected, now()?)
-                                .await
-                        }
-                        .map_err(storage)?;
+                        let record = worker
+                            .log
+                            .create_plugin_session(
+                                &maka_event_log::sessions::PluginSession {
+                                    session_id: id.clone(),
+                                    creator: namespace.clone(),
+                                    fingerprint: fingerprint.clone(),
+                                    managed: request.managed,
+                                },
+                                &expected,
+                                now()?,
+                            )
+                            .await
+                            .map_err(storage)?;
                         if worker.catalog.publish_session(&id).await.is_err() {
                             worker.begin_drain();
                         }
@@ -326,7 +320,15 @@ impl BoundCommands {
                     }
                 };
                 let manager = worker.log.session_manager(&id).await.map_err(storage)?;
-                if manager.as_ref() != request.managed.then_some(&namespace) {
+                if manager.as_ref() != request.managed.then_some(&namespace)
+                    || worker
+                        .log
+                        .session_creator(&id)
+                        .await
+                        .map_err(storage)?
+                        .as_ref()
+                        != Some(&namespace)
+                {
                     return Err(Error::Denied);
                 }
                 let current = &record.configuration;
diff --git a/crates/runtime-host/tests/fixtures/background-plugin.mjs b/crates/runtime-host/tests/fixtures/background-plugin.mjs
index f9c569edea..06e6b8d106 100644
--- a/crates/runtime-host/tests/fixtures/background-plugin.mjs
+++ b/crates/runtime-host/tests/fixtures/background-plugin.mjs
@@ -239,6 +239,27 @@ export default async function (ctx) {
           throw new Error('managed root recovery lost its identity or configuration');
         if ((await restoredCommands.restoreRoot('uncreated-root')) !== null)
           throw new Error('managed root recovery created a Session');
+        const ordinaryRequest = { ...request, operationId: 'ordinary-root', managed: false };
+        const ordinary = await commands.createRoot(ordinaryRequest);
+        const ordinaryView = await commands.session(ordinary.sessionId);
+        const changed = await commands.configure({
+          sessionId: ordinary.sessionId,
+          expectedRevision: ordinaryView.revision,
+          target: {
+            kind: 'executor',
+            executorId: 'example.background',
+            settings: { model: 'replacement' },
+          },
+        });
+        if (changed.kind !== 'committed') throw new Error('ordinary root configuration failed');
+        const restoredOrdinary = await restoredCommands.restoreRoot('ordinary-root');
+        const restoredTarget = (await restoredCommands.session(ordinary.sessionId)).target;
+        if (
+          restoredOrdinary?.sessionId !== ordinary.sessionId ||
+          restoredTarget.kind !== 'executor' ||
+          restoredTarget.settings?.model !== 'replacement'
+        )
+          throw new Error('ordinary root recovery replayed old settings');
       } finally {
         await restoredCommands.close();
       }
diff --git a/crates/runtime-host/tests/integration/plugin_commands/boundary.rs b/crates/runtime-host/tests/integration/plugin_commands/boundary.rs
index c175ebb3cf..df1d6a17b4 100644
--- a/crates/runtime-host/tests/integration/plugin_commands/boundary.rs
+++ b/crates/runtime-host/tests/integration/plugin_commands/boundary.rs
@@ -171,7 +171,7 @@ pub(super) async fn verify(
         .authorize_plugin_root_execution(
             fiber.context(),
             RootApproval {
-                template,
+                template: template.clone(),
                 source: None,
             },
         )
@@ -208,7 +208,7 @@ pub(super) async fn verify(
         roots
             .create_root(CreateRoot {
                 name: "different".into(),
-                ..request
+                ..request.clone()
             })
             .await,
         Err(CommandError::Conflict)
@@ -261,6 +261,51 @@ pub(super) async fn verify(
         commands.session(root.session_id).await,
         Err(CommandError::Denied)
     ));
+    let ordinary_request = CreateRoot {
+        managed: false,
+        operation_id: "ordinary-root".into(),
+        ..request
+    };
+    let ordinary = if reopened {
+        roots
+            .restore_root(ordinary_request.operation_id.clone())
+            .await
+            .unwrap()
+            .unwrap()
+    } else {
+        roots.create_root(ordinary_request.clone()).await.unwrap()
+    };
+    let ordinary_access = host
+        .authorize_plugin_execution(
+            foreign.context(),
+            std::slice::from_ref(&ordinary.session_id),
+        )
+        .await
+        .unwrap();
+    assert!(
+        ordinary_access
+            .session(ordinary.session_id.clone())
+            .await
+            .is_ok(),
+        "creation provenance must not impose exclusive management"
+    );
+    let fresh = host
+        .authorize_plugin_root_execution(
+            fiber.context(),
+            RootApproval {
+                template,
+                source: None,
+            },
+        )
+        .unwrap();
+    assert_eq!(
+        fresh
+            .restore_root(ordinary_request.operation_id)
+            .await
+            .unwrap(),
+        Some(ordinary)
+    );
+    assert_eq!(fresh.restore_root("absent".into()).await.unwrap(), None);
     foreign
         .shutdown(tokio::time::Instant::now() + Duration::from_secs(1))
         .await
diff --git a/crates/workhub/src/assignment.rs b/crates/workhub/src/assignment.rs
index 1ce47d1080..97eec85b8b 100644
--- a/crates/workhub/src/assignment.rs
+++ b/crates/workhub/src/assignment.rs
@@ -92,6 +92,18 @@ impl Assignment {
     }
 }
 
+async fn restore_or_create_root(
+    commands: &dyn Commands,
+    request: &CreateRoot,
+) -> Result<String, Error> {
+    Ok(
+        match commands.restore_root(request.operation_id.clone()).await? {
+            Some(session) => session.session_id,
+            None => commands.create_root(request.clone()).await?.session_id,
+        },
+    )
+}
+
 pub struct Assignments {
     pub coordinator: Arc<crate::Coordinator>,
     pub repository: Arc<Repository>,
@@ -150,7 +162,7 @@ impl Assignments {
                     let session = match &assignment.request.target {
                         Target::Existing { session_id } => session_id.clone(),
                         Target::Create { request } => {
-                            commands.create_root(*request.clone()).await?.session_id
+                            restore_or_create_root(commands.as_ref(), request).await?
                         }
                     };
                     let mut content = assignment.request.content.clone();
@@ -249,7 +261,7 @@ impl Assignments {
         match &assignment.request.target {
             Target::Create { request } => {
                 // Reattach exactly the root this grant created, including after a restart.
-                commands.create_root(*request.clone()).await?;
+                restore_or_create_root(commands.as_ref(), request).await?;
             }
             Target::Existing { session_id } => {
                 commands.session(session_id.clone()).await?;
diff --git a/packages/plugin-sdk/README.md b/packages/plugin-sdk/README.md
index 9df9b571d8..7848520bf4 100644
--- a/packages/plugin-sdk/README.md
+++ b/packages/plugin-sdk/README.md
@@ -124,7 +124,7 @@ Native endpoints accepting caller-supplied Host paths declare `Endpoint::requiri
 
 `ctx.models.search({ query })` returns enabled chat choices with supported and default thinking levels, at most 50 entries / 48 KiB; refine the query when `complete` is false. `ctx.models.resolve({ kind: 'named', connectionSlug, model })` returns the same choice shape for an exact model; `{ kind: 'default' }` resolves the current default. Neither grants execution authority or promises provider readiness.
 
-`restoreRoot(operationId)` recovers this package/scope's managed root under current workspace and source ceilings, independently of its original model. `restoreChild` requires the original child creation request. Neither creates anything; absence does not exclude a concurrent creation. `configure` advances the Session revision on every committed choice, including identical values, fencing older configuration CASes without changing event history.
+`restoreRoot(operationId)` recovers a root created by this package/scope under current workspace and source ceilings, independently of its original model. Creation provenance does not make an ordinary root exclusively managed. `restoreChild` requires the original child creation request. Neither creates anything; absence does not exclude a concurrent creation. `configure` advances the Session revision on every committed choice, including identical values, fencing older configuration CASes [...]
 
 ## Model adapters
 
diff --git a/packages/plugin-sdk/README.zh-CN.md b/packages/plugin-sdk/README.zh-CN.md
index df2c299b0a..7c1ac21f49 100644
--- a/packages/plugin-sdk/README.zh-CN.md
+++ b/packages/plugin-sdk/README.zh-CN.md
@@ -128,7 +128,7 @@ Remote 回调可以抛出携带 `RemoteFailure.code` 的 `Error`。`outcome_unkn
 
 `ctx.models.search({ query })` 返回已启用的聊天模型、支持的思考程度及默认值,每页最多 50 项/48 KiB;`complete` 为 false 时应缩小搜索范围。`ctx.models.resolve({ kind: 'named', connectionSlug, model })` 返回相同结构的精确模型选择,`{ kind: 'default' }` 解析当前默认模型。两者均不授予执行权限,也不保证提供商当前可用。
 
-`restoreRoot(operationId)` 按当前工作区和来源上限恢复本包/作用域托管的根会话,不依赖原模型。`restoreChild` 要求原始子会话创建请求。两者均不创建资源;不存在的观察不能排除并发创建。`configure` 每次成功选择都会推进 Session revision,包括相同值,以阻止较早的配置 CAS 覆盖它,不修改事件历史。
+`restoreRoot(operationId)` 按当前工作区和来源上限恢复本包/作用域创建的根会话,不依赖原模型;创建记录不会使普通根会话变成独占托管会话。`restoreChild` 要求原始子会话创建请求。两者均不创建资源;不存在的观察不能排除并发创建。`configure` 每次成功选择都会推进 Session revision,包括相同值,以阻止较早的配置 CAS 覆盖它,不修改事件历史。
 
 ## 模型适配器
 
diff --git a/packages/plugin-sdk/src/execution.ts b/packages/plugin-sdk/src/execution.ts
index 7a85673be3..351f2cc651 100644
--- a/packages/plugin-sdk/src/execution.ts
+++ b/packages/plugin-sdk/src/execution.ts
@@ -231,7 +231,7 @@ export interface Executions {
       instructions?: string | null;
     };
   }): Promise<{ sessionId: string }>;
-  /** Recover this package/scope's managed root under current workspace/source ceilings.
+  /** Recover a root created by this package/scope under current workspace/source ceilings.
    * Never creates or reconfigures it; null does not rule out a concurrent creation.
    */
   restoreRoot(operationId: string): Promise<{ sessionId: string } | null>;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org