code-mode: extend test coverage to lock in cell lifecycle (#28468)

This PR establishes the intended behavior as an executable contract
before a refactor of the cell runtime begins. It also fixes cases where
a second observer or termination request could replace an existing
response channel and leave the original caller unresolved.

### Behavior codified
- A cell can yield output and subsequently resume to completion.
- A caller can run a cell until it has no immediately runnable work,
receive its accumulated output and outstanding tool-call IDs, and then
resume the same cell when the awaited work is available.
- Each cell admits one active observer:
   - a second observer receives an explicit busy error
   - the existing observer remains registered and is not displaced
- A natural result (conclusion of the js module) that has already
reached the cell controller wins over a later termination request.
- Otherwise, termination preempts execution and resolves both:
  - the active observer, if present
  - the caller requesting termination
- Repeated termination requests are rejected while termination is
already in progress.
- Terminal responses are sent only after outstanding callback work has
been handled:
- natural completion drains notifications and cancels outstanding tool
calls
- termination cancels and drains both notification and tool callbacks.
- Cell removal and cell_closed notification happen after callback
cleanup
This commit is contained in:
Channing Conger
2026-06-16 13:34:16 -07:00
committed by GitHub
parent 4b7351700f
commit e93516e259
3 changed files with 903 additions and 137 deletions
+9 -6
View File
@@ -1411,7 +1411,7 @@ text("phase 3");
#[cfg_attr(windows, ignore = "no exec_command on Windows")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn code_mode_yield_timeout_works_for_busy_loop() -> Result<()> {
async fn code_mode_yield_and_termination_are_not_starved_by_runtime_output() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -1420,8 +1420,12 @@ async fn code_mode_yield_timeout_works_for_busy_loop() -> Result<()> {
});
let test = builder.build(&server).await?;
let code = r#"// @exec: {"yield_time_ms": 100}
text("phase 1");
// Exact controller arbitration is covered by deterministic code-mode contract tests. Keep
// this end-to-end load bounded while exercising a substantial runtime output backlog.
let code = r#"// @exec: {"yield_time_ms": 0, "max_output_tokens": 16}
for (let index = 0; index < 16_384; index++) {
text(`event ${index}`);
}
while (true) {}
"#;
@@ -1451,7 +1455,7 @@ while (true) {}
let first_request = first_completion.single_request();
let first_items = custom_tool_output_items(&first_request, "call-1");
assert_eq!(first_items.len(), 2);
assert_eq!(first_items.len(), 1);
assert_regex_match(
concat!(
r"(?s)\A",
@@ -1459,7 +1463,6 @@ while (true) {}
),
text_item(&first_items, /*index*/ 0),
);
assert_eq!(text_item(&first_items, /*index*/ 1), "phase 1");
let cell_id = extract_running_cell_id(text_item(&first_items, /*index*/ 0));
responses::mount_sse_once(
@@ -1491,7 +1494,7 @@ while (true) {}
let second_request = second_completion.single_request();
let second_items = function_tool_output_items(&second_request, "call-2");
assert_eq!(second_items.len(), 1);
assert!(!second_items.is_empty());
assert_regex_match(
concat!(
r"(?s)\A",