fix: harden Memory restore and scheduling

This commit is contained in:
2026-09-05 00:24:00 +09:00
parent 4df277c81f
commit aa96bbedbc
3 changed files with 197 additions and 69 deletions
@@ -620,6 +620,48 @@ mod tests {
assert_eq!(plan.resident_summary.as_deref(), Some("# Durable Memory"));
}
#[tokio::test]
async fn memory_prompt_contribution_rereads_resident_summary_for_each_install() {
let prompts = crate::prompt::catalog::PromptCatalog::builtins_only().unwrap();
let mut config = manifest::ResolvedMemoryFeatureConfig::default();
config.profile.enabled = true;
config
.bind_workspace_settings(manifest::WorkspaceMemorySettingsSnapshot {
workspace_id: "workspace".to_string(),
settings_revision: 1,
language: "English".to_string(),
})
.unwrap();
let first = MemoryFeatureInstallPlan::prepare_resolved(
config.clone(),
resident_client("first resident summary"),
prompts.clone(),
None,
)
.await
.unwrap()
.unwrap();
let restored = MemoryFeatureInstallPlan::prepare_resolved(
config,
resident_client("updated resident summary"),
prompts,
None,
)
.await
.unwrap()
.unwrap();
assert_eq!(
first.resident_summary.as_deref(),
Some("first resident summary")
);
assert_eq!(
restored.resident_summary.as_deref(),
Some("updated resident summary")
);
}
#[test]
fn memory_feature_owns_normal_and_staging_tool_surfaces() {
let normal = MemoryToolsFeature::new(test_client(), false);
@@ -77,7 +77,10 @@ impl MemoryLifecycleFeature {
workspace_context: WorkerWorkspaceContext,
event_tx: Option<broadcast::Sender<Event>>,
) -> std::io::Result<Option<Self>> {
if !lifecycle_enabled || !config.profile.enabled || !config.profile.extraction.enabled {
if !lifecycle_enabled
|| !config.profile.enabled
|| (!config.profile.extraction.enabled && !config.profile.consolidation.request_enabled)
{
return Ok(None);
}
config
@@ -521,9 +524,12 @@ impl FeatureBackgroundTask for MemoryLifecycleTask {
context: BackgroundTaskContext,
cancellation: BackgroundTaskCancellation,
) -> Result<(), HookError> {
let extraction = self
.run_extraction(context.clone(), cancellation.clone())
.await;
let extraction = if self.config.profile.extraction.enabled {
self.run_extraction(context.clone(), cancellation.clone())
.await
} else {
Ok(())
};
if !cancellation.is_cancelled() {
context.generation_fence.ensure_current()?;
if self.config.profile.consolidation.request_enabled {
@@ -1355,6 +1361,38 @@ permission = "write"
);
}
#[tokio::test]
async fn lifecycle_task_requests_consolidation_when_extraction_is_disabled() {
let client = ScriptClient::new(Vec::new());
let extension_writes = Arc::new(Mutex::new(Vec::new()));
let (event_tx, _) = broadcast::channel(16);
let workspace_client = Arc::new(RecordingWorkspaceClient::default());
let mut interrupted = capture(2, 250);
interrupted.run_exit = CommittedRunExit::Interrupted;
let mut task = test_task(
interrupted,
Box::new(client),
extension_writes.clone(),
event_tx,
workspace_client.clone(),
);
task.config.profile.extraction.enabled = false;
task.config.profile.consolidation.request_enabled = true;
run_background_task(task).await;
assert!(extension_writes.lock().unwrap().is_empty());
let requests = workspace_client.requests.lock().unwrap();
assert!(
requests.iter().any(|request| {
request
.body
.as_deref()
.is_some_and(|body| body == "{\"force\":false}")
}),
"recorded requests: {requests:?}"
);
}
fn internal_result(
lifecycle: WorkerRunResult,
) -> Result<InternalWorkerResult, InternalWorkerError> {