feat: page console history across compaction

This commit is contained in:
2026-09-30 08:13:07 +09:00
parent cafbb73a22
commit 34ed988b75
38 changed files with 3107 additions and 144 deletions
+24
View File
@@ -477,6 +477,13 @@ pub struct WorkerSessionObservationRequest {
pub worker_ref: WorkerRef,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkerSessionHistoryRequest {
pub worker_ref: WorkerRef,
pub cursor: Option<String>,
pub limit: Option<usize>,
}
pub trait WorkerExecutionBackend: Send + Sync + 'static {
fn backend_id(&self) -> &str;
@@ -497,6 +504,16 @@ pub trait WorkerExecutionBackend: Send + Sync + 'static {
}
}
fn worker_session_history(
&self,
_request: WorkerSessionHistoryRequest,
) -> runtime_api::WorkerSessionHistoryAvailability {
runtime_api::WorkerSessionHistoryAvailability::Unavailable {
reason: runtime_api::WorkerSessionHistoryUnavailableReason::Unsupported,
message: "session history paging is not supported by this Runtime".to_string(),
}
}
fn spawn_worker(&self, request: WorkerExecutionSpawnRequest) -> WorkerExecutionSpawnResult;
/// Read-only restore validation performed before any live controller,
@@ -726,6 +743,13 @@ impl WorkerExecutionBackendRef {
self.backend.worker_session(request)
}
pub(crate) fn worker_session_history(
&self,
request: WorkerSessionHistoryRequest,
) -> runtime_api::WorkerSessionHistoryAvailability {
self.backend.worker_session_history(request)
}
pub(crate) fn spawn_worker(
&self,
request: WorkerExecutionSpawnRequest,
@@ -260,6 +260,35 @@ impl runtime_api::RuntimeApi for RuntimeManagementApi {
.map_err(api_error)
}
async fn worker_session_history(
&self,
worker_id: String,
request: runtime_api::WorkerSessionHistoryRequest,
) -> Result<runtime_api::WorkerSessionHistoryAvailability, runtime_api::RuntimeApiError> {
let worker_ref = worker_ref_for(&self.state.runtime, worker_id).map_err(api_error)?;
let scope = auth_workspace_scope(&self.state, auth_extension().as_ref())
.map_err(api_error)?
.ok_or_else(|| {
runtime_api::RuntimeApiError::new(
StatusCode::FORBIDDEN.as_u16(),
"runtime_worker_session_scope_required",
"Worker Session history requires a Workspace-scoped capability",
)
})?;
if scope.workspace_id != request.workspace_id {
return Err(runtime_api::RuntimeApiError::new(
StatusCode::FORBIDDEN.as_u16(),
"runtime_worker_session_workspace_scope_mismatch",
"Worker Session Workspace scope does not match the authenticated capability",
));
}
self.state
.runtime
.worker_session_history_scoped(&scope, &worker_ref, request.cursor, request.limit)
.map_err(RuntimeHttpRestError::runtime)
.map_err(api_error)
}
async fn create_worker(
&self,
value: runtime_api::CreateWorkerRequest,
+34 -1
View File
@@ -16,7 +16,8 @@ use crate::execution::{
WorkerExecutionBackend, WorkerExecutionBackendRef, WorkerExecutionHandle,
WorkerExecutionOperation, WorkerExecutionResult, WorkerExecutionSpawnRequest,
WorkerExecutionSpawnResult, WorkerExecutionStopRequest, WorkerLifecycleOperationId,
WorkerSessionObservationRequest, WorkspaceConfigFetchRequest, WorkspaceConfigFetchResult,
WorkerSessionHistoryRequest, WorkerSessionObservationRequest, WorkspaceConfigFetchRequest,
WorkspaceConfigFetchResult,
};
#[cfg(feature = "fs-store")]
use crate::fs_store::{
@@ -1613,6 +1614,38 @@ impl Runtime {
})
}
/// Read one bounded page from the adopted durable Session lineage without
/// restoring the Worker or modifying model context.
pub fn worker_session_history_scoped(
&self,
scope: &RuntimeWorkspaceScope,
worker_ref: &WorkerRef,
cursor: Option<String>,
limit: Option<usize>,
) -> Result<runtime_api::WorkerSessionHistoryAvailability, RuntimeError> {
let operation_lock = self.worker_operation_lock(worker_ref.worker_id)?;
let _operation_guard = operation_lock
.lock()
.map_err(|_| RuntimeError::StatePoisoned)?;
self.ensure_worker_in_workspace(scope, worker_ref)?;
let backend = {
let state = self.lock()?;
state.worker(worker_ref)?;
state.execution_backend.clone()
};
Ok(match backend {
Some(backend) => backend.worker_session_history(WorkerSessionHistoryRequest {
worker_ref: worker_ref.clone(),
cursor,
limit,
}),
None => runtime_api::WorkerSessionHistoryAvailability::Unavailable {
reason: runtime_api::WorkerSessionHistoryUnavailableReason::Unsupported,
message: "session history paging is not supported by this Runtime".to_string(),
},
})
}
/// Attach a live execution through a workspace-scoped Runtime authorization context.
pub fn restore_worker_scoped(
&self,
@@ -172,6 +172,15 @@ pub trait RuntimeWorkerFactory: Send + Sync + 'static {
Err(session_store::RetainedSnapshotReadError::RetentionMissing)
}
fn retained_session_history_page(
&self,
_worker_ref: &WorkerRef,
_cursor: Option<&str>,
_limit: Option<usize>,
) -> Result<protocol::SessionHistoryPage, session_store::RetainedHistoryReadError> {
Err(session_store::RetainedHistoryReadError::RetentionMissing)
}
async fn spawn_controller(
&self,
request: WorkerExecutionSpawnRequest,
@@ -1027,6 +1036,25 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
)
}
fn retained_session_history_page(
&self,
worker_ref: &WorkerRef,
cursor: Option<&str>,
limit: Option<usize>,
) -> Result<protocol::SessionHistoryPage, session_store::RetainedHistoryReadError> {
let aggregate_dir = self
.worker_aggregate_dir(worker_ref)
.map_err(|_| session_store::RetainedHistoryReadError::RetentionMissing)?;
let worker_name = Self::runtime_worker_name_for_ref(worker_ref);
session_store::read_retained_session_history_page(
&aggregate_dir,
&worker_name,
cursor,
limit,
session_store::RetainedHistoryReadLimits::default(),
)
}
fn observe_workspace_prompt_projection(
&self,
projection: worker::WorkspacePromptProjection,
@@ -2216,6 +2244,48 @@ where
}
}
fn worker_session_history(
&self,
request: crate::execution::WorkerSessionHistoryRequest,
) -> runtime_api::WorkerSessionHistoryAvailability {
match self.factory.retained_session_history_page(
&request.worker_ref,
request.cursor.as_deref(),
request.limit,
) {
Ok(page) => runtime_api::WorkerSessionHistoryAvailability::Page { page },
Err(error) => {
let reason = match error {
session_store::RetainedHistoryReadError::RetentionMissing => {
runtime_api::WorkerSessionHistoryUnavailableReason::RetentionMissing
}
session_store::RetainedHistoryReadError::ActivePointerMissing => {
runtime_api::WorkerSessionHistoryUnavailableReason::ActivePointerMissing
}
session_store::RetainedHistoryReadError::MigrationRequired => {
runtime_api::WorkerSessionHistoryUnavailableReason::MigrationRequired
}
session_store::RetainedHistoryReadError::CorruptLog => {
runtime_api::WorkerSessionHistoryUnavailableReason::CorruptLog
}
session_store::RetainedHistoryReadError::StorageUnavailable => {
runtime_api::WorkerSessionHistoryUnavailableReason::StorageUnavailable
}
session_store::RetainedHistoryReadError::InvalidCursor => {
runtime_api::WorkerSessionHistoryUnavailableReason::InvalidCursor
}
session_store::RetainedHistoryReadError::ResourceLimit => {
runtime_api::WorkerSessionHistoryUnavailableReason::ResourceLimit
}
};
runtime_api::WorkerSessionHistoryAvailability::Unavailable {
reason,
message: error.to_string(),
}
}
}
}
fn observe_workspace_prompt_projection(
&self,
projection: worker::WorkspacePromptProjection,