Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0b4c80e
chore(deps): update dependency @modelcontextprotocol/client to v2.4.0
renovate[bot] Oct 23, 2026
911c359
build(plugin): regenerate embedded host bundles
github-actions[bot] Oct 23, 2026
04aaf4a
fix(artifacts): remove artifact size limits
ScriptedAlchemy Oct 23, 2026
7ba08c9
fix(workflow): remove stale artifact storage size cap
ScriptedAlchemy Oct 23, 2026
deee144
Merge remote-tracking branch 'origin/master' into renovate/modelconte…
ScriptedAlchemy Oct 24, 2026
6f14616
chore: merge master into dependency update
ScriptedAlchemy Oct 24, 2026
a928552
chore: merge master bridge lifecycle fixes
ScriptedAlchemy Oct 24, 2026
1119705
test(codex): verify activation without repairing its cache
ScriptedAlchemy Oct 24, 2026
1fecbef
chore: merge MCP server update from master
ScriptedAlchemy Oct 24, 2026
dc524ef
fix(session): retain summary work after live projection
ScriptedAlchemy Oct 24, 2026
42d3b2a
chore: merge master SDK cancellation fixes
ScriptedAlchemy Oct 24, 2026
d7b7857
fix(session): recheck summaries without replaying projection
ScriptedAlchemy Oct 24, 2026
7f9f077
chore: merge latest master session fixes
ScriptedAlchemy Oct 24, 2026
15aac73
chore: merge master memory status updates
ScriptedAlchemy Oct 24, 2026
dafd930
build(plugin): regenerate chatgpt embed after master merge
ScriptedAlchemy Oct 24, 2026
21fdc34
chore: merge master and regenerate chatgpt embed
ScriptedAlchemy Oct 24, 2026
8d0443b
chore: stack #4478 on #4473
ScriptedAlchemy Oct 25, 2026
d6cd868
test(session-runtime): keep fake summarizer under default budget (#4521)
ScriptedAlchemy Oct 25, 2026
be21c0c
test(runtime): wait for text readiness before testing cursors
ScriptedAlchemy Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions crates/tracedecay-agent-hosts/src/agents/codex/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1042,14 +1042,25 @@ fn activation_retires_released_skill_files_and_converges() {
!retired_dir.exists(),
"activation must delete the retired skill and its emptied directory"
);
copy_rendered_bundle_to_native_cache(home.path(), TEST_BIN);
// The cache was current before adding the retired source-only skill.
// Observe activation's result directly instead of repairing the fixture.
assert!(
codex_source_manifest_matches_catalog_version(home.path()).unwrap(),
"activation must preserve the current source manifest"
);
assert!(
codex_plugin_enabled(home.path()).unwrap(),
"activation must preserve the enabled plugin key: {:?}",
load_toml_file(&codex_config_path(home.path())).unwrap()
);
assert!(
codex_plugin_is_natively_active(
profile,
home.path(),
Some((TEST_BIN, &ServeArgs::default()))
)
.unwrap()
.unwrap(),
"activation must leave the source and native cache current"
);
}

Expand Down
4 changes: 2 additions & 2 deletions crates/tracedecay-session-runtime/src/lcm_effects/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1568,7 +1568,7 @@ fn parked_sessions_converge_once_the_summarizer_becomes_available_without_restar
&db,
&project_root,
LcmSummarizerExecutablesV1 {
cursor_agent: LcmSummarizerExecutableV1::configured_with(cursor_bin, None, Some(5))
cursor_agent: LcmSummarizerExecutableV1::configured_with(cursor_bin, None, None)
.unwrap(),
codex: LcmSummarizerExecutableV1::Unconfigured,
},
Expand Down Expand Up @@ -1712,7 +1712,7 @@ fn refresh_begins_at_the_committed_frontier_after_background_summaries_publish()
)
.unwrap();
fixture.pin(LcmSummarizerExecutablesV1 {
cursor_agent: LcmSummarizerExecutableV1::configured_with(cursor_bin, None, Some(5))
cursor_agent: LcmSummarizerExecutableV1::configured_with(cursor_bin, None, None)
.unwrap(),
codex: LcmSummarizerExecutableV1::Unconfigured,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ pub(super) async fn run_session_temporal_refresh_scheduler(
) {
let mut retry_attempt = 0u32;
let mut summary_retry_attempt = 0u32;
let mut summary_recheck = false;
let mut history_priority_passes = 0u32;
let mut history_release = None;
let mut granted_history_admission = None;
Expand Down Expand Up @@ -265,11 +266,14 @@ pub(super) async fn run_session_temporal_refresh_scheduler(
history_requested = false;
projection_requested = true;
}
if !projection_requested && !history_requested {
if !projection_requested && !history_requested && !summary_recheck {
break;
}
summary_recheck = false;
state.begin_pass();
state.pass_count.fetch_add(1, Ordering::AcqRel);
if projection_requested || history_requested {
state.pass_count.fetch_add(1, Ordering::AcqRel);
}
// A granted turn this iteration does not spend on history is
// returned before the summary page queues for a permit of its own.
let granted = granted_history_admission
Expand Down Expand Up @@ -426,7 +430,7 @@ pub(super) async fn run_session_temporal_refresh_scheduler(
// registered to receive one.
state.mark_worker_idle();
state.idle.notify_waiters();
let Some(permit) = live_projection
let Some((permit, _)) = live_projection
.wait_for(&mut admission, &mut projection_joins, &mut report, true)
.await
else {
Expand Down Expand Up @@ -466,13 +470,14 @@ pub(super) async fn run_session_temporal_refresh_scheduler(
),
};
tokio::pin!(page);
let Some(result) = live_projection
let Some((result, recheck)) = live_projection
.wait_for(&mut page, &mut projection_joins, &mut report, false)
.await
else {
return;
};
drop(permit);
summary_recheck = recheck;
result
};
match summary_result {
Expand Down Expand Up @@ -920,16 +925,17 @@ impl LiveProjectionDuringSummary<'_> {
stage: F,
joins: &mut Vec<SessionProjectionJoin>,
report: &mut SessionTemporalRefreshPassReport,
idle_between_projections: bool,
) -> Option<F::Output>
waiting_for_admission: bool,
) -> Option<(F::Output, bool)>
where
F: Future + Send,
F::Output: Send,
{
tokio::pin!(stage);
let mut retry_attempt = 0u32;
let mut retry_at = None;
loop {
let mut summary_pass_owed = false;
let result = loop {
let projection_wake = async {
let wake = self.state.wake.notified();
if let Some(deadline) = retry_at {
Expand All @@ -947,7 +953,7 @@ impl LiveProjectionDuringSummary<'_> {
let woke = tokio::select! {
biased;
() = self.state.wait_for_cancellation() => return None,
result = &mut stage => return Some(result),
result = &mut stage => break result,
woke = projection_wake => woke,
};
if !woke {
Expand All @@ -956,6 +962,7 @@ impl LiveProjectionDuringSummary<'_> {
if !self.state.take_dirty() {
continue;
}
summary_pass_owed |= !waiting_for_admission;
self.state.mark_worker_busy();
self.state.begin_pass();
self.state.pass_count.fetch_add(1, Ordering::AcqRel);
Expand Down Expand Up @@ -1013,13 +1020,17 @@ impl LiveProjectionDuringSummary<'_> {
}
}
if let Some(result) = completed_stage {
return Some(result);
break result;
}
if idle_between_projections {
if waiting_for_admission {
self.state.mark_worker_idle();
self.state.idle.notify_waiters();
}
}
};
// A running page may have read its inputs before the consumed wake.
// Its follow-up needs summary convergence, not another projection of
// the frontier this wake already served. Admission has no page yet.
Some((result, summary_pass_owed))
}
}

Expand Down Expand Up @@ -2209,6 +2220,80 @@ mod tests {
use tracedecay_sessions::runtime::{SessionMessageRecord, SessionRecord};
use tracedecay_store::{SessionRefreshReceiptRequestV1, SessionRefreshTerminalStateV1};

#[tokio::test]
async fn a_live_wake_during_summary_retains_one_convergence_pass() {
for (name, waiting_for_admission, cancel, owes_convergence) in [
("completed-page", false, false, true),
("completed-admission", true, false, false),
("cancelled-page", false, true, false),
] {
let harness = RegisteredGlobalDbHarness::open(name).await;
let state = Arc::new(SessionTemporalRefreshWakeState::default());
let entered = Arc::new(tokio::sync::Notify::new());
let finish = Arc::new(tokio::sync::Notify::new());
let worker = tokio::spawn({
let database = harness.registered.clone();
let state = Arc::clone(&state);
let entered = Arc::clone(&entered);
let finish = Arc::clone(&finish);
async move {
let stage = async {
entered.notify_one();
finish.notified().await;
};
let live_projection = LiveProjectionDuringSummary {
database: &database,
state: &state,
projector: &CanonicalSessionTemporalProjector,
policy: SessionTemporalRefreshPolicy::default(),
};
let mut joins = Vec::new();
let mut report = SessionTemporalRefreshPassReport::default();
live_projection
.wait_for(stage, &mut joins, &mut report, waiting_for_admission)
.await
}
});
entered.notified().await;

// A running page may already have checked the old summarizer
// binding. Publishing this empty temporal frontier cannot cover
// the convergence work requested by a later configuration wake.
assert!(
state
.handle()
.wake_and_wait_for_projection(Duration::from_secs(5))
.await,
"{name}: live projection must join before the held stage finishes"
);
assert!(!worker.is_finished(), "{name}: stage must remain in flight");
assert!(
!state.dirty.load(Ordering::Acquire),
"{name}: do not spin on the consumed wake while the stage is in flight"
);

if cancel {
state.cancel();
} else {
finish.notify_one();
}
let completed = tokio::time::timeout(Duration::from_secs(5), worker)
.await
.expect("held stage must settle")
.expect("worker must join");
assert_eq!(
completed,
(!cancel).then_some(((), owes_convergence)),
"{name}: a consumed wake still owes convergence after a running page"
);
assert_eq!(state.pass_count.load(Ordering::Acquire), 1, "{name}");
assert!(
!state.take_dirty(),
"{name}: summary convergence must not replay the consumed projection"
);
}
}

#[tokio::test]
async fn an_in_flight_summary_stage_keeps_polling_while_a_live_wake_projects() {
let harness = RegisteredGlobalDbHarness::open("refresh-during-summary-writer").await;
Expand Down Expand Up @@ -2285,7 +2370,10 @@ mod tests {
assert_eq!(receipt.state(), SessionRefreshTerminalStateV1::Complete);
assert!(!worker.is_finished(), "the summary stage remains in flight");
finish.notify_one();
assert_eq!(worker.await.expect("summary stage completes"), Some(17));
assert_eq!(
worker.await.expect("summary stage completes"),
Some((17, true))
);
}

/// A lease the store has fenced resubmits the identical request on every
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,36 @@ impl ProductionFixture {
fn home(&self) -> &Path {
self._environment.home()
}

fn wait_for_query_readiness(&self) {
// Symbol reads can use a retained decode while the text owner warms.
// Continuations also exercise exact and phrase search, so both owners
// must serve before their pagination behavior is tested.
let arguments = serde_json::json!({
"format": "json",
"wait_for": {
"state": "ready",
"timeout_ms": u64::try_from(READINESS_RETRY_DEADLINE.as_millis())
.expect("readiness budget fits u64"),
},
});
let output = common::tracedecay_command_with_home(self.home())
.current_dir(&self.project)
.args(["tool", "--project"])
.arg(&self.project)
.args(["status", "--args"])
.arg(arguments.to_string())
.stdin(Stdio::null())
.output()
.expect("wait for code query readiness");
assert_command_success("code query readiness", &output);
let status: Value = serde_json::from_slice(&output.stdout).expect("status JSON");
assert_eq!(
status["wait"],
serde_json::json!({ "outcome": "reached" }),
"text and graph queries must be ready before pagination: {status}"
);
}
}

/// Opens the shipped daemon over an indexed project whose symbol graph is large
Expand Down Expand Up @@ -1399,6 +1429,7 @@ async fn continuation_result(
async fn every_cursor_carrying_code_operation_mints_and_spends_a_continuation() {
let fixture = production_fixture().await;
let anchors = ProbeAnchors::resolve(&fixture).await;
fixture.wait_for_query_readiness();
let cases = continuation_cases();

let declared = cases
Expand Down
58 changes: 29 additions & 29 deletions plugin/chatgpt-extension/embedded/app.html

Large diffs are not rendered by default.

34 changes: 17 additions & 17 deletions plugin/chatgpt-extension/embedded/server.mjs

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading