blob: 4672de1565fad58ac1aecd24148a12b4cc668d38 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use super::*;
async fn cancelled(fixture: &Fixture, listener: &TcpListener, session: &str) -> Value {
let sent = fixture.send(session).await.unwrap();
let (mut socket, _) = listener.accept().await.unwrap();
request(&mut socket).await;
let stopped = fixture
.mutation(
2,
"stop",
json!({
"sessionId":session, "invocationId":sent["invocationId"]
}),
Uuid::new_v4(),
)
.await
.unwrap();
assert_eq!(stopped["requested"], true);
fixture.drained().await;
let read = fixture
.query(
"execution",
json!({
"sessionId":session, "invocationId":sent["invocationId"]
}),
)
.await
.unwrap();
assert_eq!(read["execution"]["state"], "cancelled");
read["execution"].clone()
}
async fn resume_input(fixture: &Fixture, source: &Value) -> Value {
json!({"sessionId":source["sessionId"], "sourceInvocationId":source["invocationId"],
"sourceHighWater":source["highWater"], "model":fixture.selection().await})
}
fn assert_computer_state(request: &Value, expected: &str) {
let tool = request["tools"]
.as_array()
.unwrap()
.iter()
.find(|tool| tool["function"]["name"] == "cua_repl")
.expect("real Computer tool descriptor");
assert!(
tool["function"]["description"]
.as_str()
.unwrap()
.contains(&format!("Current REPL state: {expected}.")),
"{tool}"
);
}
async fn assert_computer_output(fixture: &Fixture, session: &str, expected: &str) {
use maka_agent_facts::event::{Fact, ToolOutcome};
use maka_agent_facts::tool_output::DurableToolProjection;
let facts = service_tools::facts(fixture, session).await;
let outcome = facts
.iter()
.rev()
.find_map(|fact| match fact {
Fact::ToolSettled { outcome, .. } => Some(outcome),
_ => None,
})
.expect("actual Computer settlement");
let ToolOutcome::Succeeded {
model_projection: DurableToolProjection::Text { text },
..
} = outcome
else {
panic!("expected successful Computer text output: {outcome:?}");
};
assert_eq!(text, &format!("{expected}\nREPL state: \"ready\""));
}
async fn computer_resume_replays_original_input_without_duplicate_effects(stop: bool) {
use maka_agent_facts::event::Fact;
use maka_agent_facts::interaction::InteractionRequest;
const SECRET: &str = "SECRET_MAIN_PROVIDER_BODY_SENTINEL";
tokio::time::timeout(Duration::from_secs(90), async {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let fixture = Fixture::with_services(
&format!("http://{}/v1", listener.local_addr().unwrap()),
None,
None,
Some(service_tools::Setup::computer()),
)
.await;
let session = fixture.create("Resume with Computer").await;
// Establish a real approved Computer VM before interrupting another run.
fixture.send(&session).await.unwrap();
let (mut socket, _) = listener.accept().await.unwrap();
assert_computer_state(&request(&mut socket).await, "fresh");
service_tools::named_tool_response(
&mut socket,
"cua_repl",
json!({"code":"globalThis.resumeCounter = 40; nodeRepl.write(globalThis.resumeCounter);"}),
)
.await;
let mut commits = fixture.store.subscribe_commits();
let approval = loop {
commits.borrow_and_update();
if let Some(record) = fixture
.store
.pending_interactions(&session)
.await
.unwrap()
.into_iter()
.next()
{
break record;
}
commits.changed().await.unwrap();
};
assert!(matches!(&approval.request, InteractionRequest::ClientCapability { target, .. } if target == &maka_workspace_api::computer::target()));
assert_eq!(fixture.mutation(2, "respond", json!({"sessionId":session,"requestId":approval.request_id,"answer":{"kind":"client_capability","decision":"allow"}}), Uuid::new_v4()).await.unwrap()["kind"], "recorded");
let (mut socket, _) = listener.accept().await.unwrap();
request(&mut socket).await;
assert_computer_output(&fixture, &session, "40").await;
response(&mut socket).await;
fixture.drained().await;
let sent = fixture.send(&session).await.unwrap();
let (mut socket, _) = listener.accept().await.unwrap();
assert_computer_state(&request(&mut socket).await, "ready");
if stop {
assert_eq!(fixture.mutation(2, "stop", json!({"sessionId":session,"invocationId":sent["invocationId"]}), Uuid::new_v4()).await.unwrap()["requested"], true);
} else {
let body = json!({"error":{"message":SECRET,"type":"invalid_request_error","code":"invalid_request"}}).to_string();
socket.write_all(format!("HTTP/1.1 400 Bad Request\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len()).as_bytes()).await.unwrap();
}
drop(socket);
fixture.drained().await;
let source = fixture.query("execution", json!({"sessionId":session,"invocationId":sent["invocationId"]})).await.unwrap()["execution"].clone();
assert_eq!(source["state"], if stop { "cancelled" } else { "failed" }, "{source}");
let failed_invocation=fixture.store.invocation_boundary(&session,sent["invocationId"].as_str().unwrap()).await.unwrap().unwrap().0.invocation;
let diagnostics=fixture.query("diagnostics",json!({"sessionId":session,"position":{"kind":"latest"}})).await;
// A later message must not replace the selected source's original input.
fixture.mutation(2, "send", json!({"sessionId":session,"message":"Later unrelated Computer request","model":fixture.selection().await}), Uuid::new_v4()).await.unwrap();
let (mut socket, _) = listener.accept().await.unwrap();
request(&mut socket).await;
response(&mut socket).await;
fixture.drained().await;
let input = resume_input(&fixture, &source).await;
let operation = Uuid::new_v4();
let resumed = fixture.mutation(2, "resume", input.clone(), operation).await.unwrap();
assert_eq!(resumed["kind"], "started", "{resumed}");
assert_ne!(resumed["execution"]["invocationId"], source["invocationId"]);
let (mut socket, _) = listener.accept().await.unwrap();
let replay = request(&mut socket).await;
assert_computer_state(&replay, "ready");
let last_user = replay["messages"].as_array().unwrap().iter().rev().find(|message| message["role"] == "user").unwrap();
assert_eq!(last_user["content"], "Hello");
assert!(!replay.to_string().contains("Later unrelated Computer request"));
service_tools::named_tool_response(&mut socket, "cua_repl", json!({"code":"nodeRepl.write(++globalThis.resumeCounter);"})).await;
let (mut socket, _) = listener.accept().await.unwrap();
request(&mut socket).await;
assert_computer_output(&fixture, &session, "41").await;
response(&mut socket).await;
fixture.drained().await;
let completed = fixture.query("execution", json!({"sessionId":session,"invocationId":resumed["execution"]["invocationId"]})).await.unwrap();
assert_eq!(completed["execution"]["state"], "completed");
assert_eq!(fixture.mutation(2, "resume", input.clone(), operation).await.unwrap(), resumed);
assert_eq!(fixture.mutation(2, "resume", input, Uuid::new_v4()).await.unwrap(), json!({"kind":"parked","reason":"already_continued"}));
assert_eq!(fixture.query("execution", json!({"sessionId":session,"invocationId":source["invocationId"]})).await.unwrap()["execution"], source);
let facts = service_tools::facts(&fixture, &session).await;
assert_eq!(facts.iter().filter(|fact| matches!(fact, Fact::ToolDispatched { name, input, .. } if name == "cua_repl" && input["code"] == "nodeRepl.write(++globalThis.resumeCounter);")).count(), 1);
// Read the real VM again: duplicate resume must not have incremented it.
fixture.send(&session).await.unwrap();
let (mut socket, _) = listener.accept().await.unwrap();
assert_computer_state(&request(&mut socket).await, "ready");
service_tools::named_tool_response(&mut socket, "cua_repl", json!({"code":"nodeRepl.write(globalThis.resumeCounter);"})).await;
let (mut socket, _) = listener.accept().await.unwrap();
request(&mut socket).await;
assert_computer_output(&fixture, &session, "41").await;
response(&mut socket).await;
fixture.drained().await;
assert!(fixture.store.pending_interactions(&session).await.unwrap().is_empty());
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
if !stop {
let diagnostics=diagnostics.expect("actual main HTTP failure must be readable through public diagnostics");
let entries=diagnostics["entries"].as_array().unwrap();
assert_eq!(entries.len(),2,"both original model-step and terminal execution evidence must be visible");
for entry in entries {
assert!(entry["sequence"].as_u64().is_some_and(|sequence|sequence>0));
assert!(entry["eventId"].as_str().is_some_and(|id|!id.is_empty()));
assert!(entry["recordedAt"].as_f64().is_some());
assert_eq!(entry["detail"]["invocation"],serde_json::to_value(&failed_invocation).unwrap());
assert_eq!(entry["detail"]["modelFailure"],json!({"kind":"http","status":400}));
}
let execution=entries.iter().find(|entry|entry["detail"]["kind"] == "execution").unwrap();
assert_eq!(execution["detail"]["reason"],"model_http");
let step=entries.iter().find(|entry|entry["detail"]["kind"] == "model_step").unwrap();
assert_eq!(step["detail"]["status"],"failed");
let interrupted=facts.iter().find(|fact|matches!(fact,Fact::ModelInterrupted {..})).unwrap();
let ended=facts.iter().find(|fact|matches!(fact,Fact::InvocationEnded {outcome:maka_agent_facts::event::InvocationOutcome::Failed {..}})).unwrap();
assert_eq!(serde_json::to_value(interrupted).unwrap()["model_failure"],json!({"kind":"http","status":400}));
assert_eq!(serde_json::to_value(ended).unwrap()["outcome"]["model_failure"],json!({"kind":"http","status":400}));
assert!(!diagnostics.to_string().contains(SECRET),"safe diagnostics must not expose provider response bodies");
}
}).await.unwrap();
}
#[tokio::test]
async fn computer_resume_stopped_replays_original_input_without_duplicate_effects() {
computer_resume_replays_original_input_without_duplicate_effects(true).await;
}
#[tokio::test]
async fn computer_resume_model_failure_replays_original_input_without_duplicate_effects() {
computer_resume_replays_original_input_without_duplicate_effects(false).await;
}
#[tokio::test]
async fn exact_receipt_resumes_original_input_and_deduplicates_after_prerequisites_disappear() {
tokio::time::timeout(Duration::from_secs(45), async {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let fixture = Fixture::new(&format!("http://{}/v1", listener.local_addr().unwrap()), None).await;
let session = fixture.create("Resume original").await;
let source = cancelled(&fixture, &listener, &session).await;
let boundary = fixture.store.invocation_boundary(&session,source["invocationId"].as_str().unwrap()).await.unwrap().unwrap().0;
let maka_agent_facts::input::InvocationInput::Message {source_messages,..} = boundary.root_input() else {panic!("original message")};
let message = source_messages[0].message.message_id.clone();
let later = fixture.mutation(2, "send", json!({"sessionId":session, "message":"Later separate request", "model":fixture.selection().await}), Uuid::new_v4()).await.unwrap();
let (mut socket, _) = listener.accept().await.unwrap();
request(&mut socket).await;
response(&mut socket).await;
fixture.drained().await;
let read = fixture.query("execution", json!({"sessionId":session,"invocationId":source["invocationId"]})).await.unwrap();
assert_eq!(read["execution"], source, "exact receipt must not become the Session tip");
let other = fixture.create("Other session").await;
assert_eq!(fixture.query("execution", json!({"sessionId":other,"invocationId":source["invocationId"]})).await.unwrap(), json!({"kind":"missing"}));
assert!(fixture.query("execution", json!({"sessionId":"foreign","invocationId":source["invocationId"]})).await.is_err());
let input = resume_input(&fixture, &source).await;
let operation = Uuid::new_v4();
let prepared = fixture.app.client().prepare_mutation(fixture.binding(2).await, "resume".into(), input.clone(), scope(&fixture.issuer, true), operation, Attachments::new().into()).await.unwrap();
let reference = prepared.reference().clone();
drop(prepared);
let resumed = fixture.mutation(2, "resume", input.clone(), operation).await.unwrap();
assert_eq!(resumed["kind"], "started", "{resumed}");
let execution = &resumed["execution"];
assert_ne!(execution["invocationId"], source["invocationId"]);
assert_ne!(execution["invocationId"], later["invocationId"]);
let (mut socket, _) = listener.accept().await.unwrap();
let replay = request(&mut socket).await;
assert!(replay["messages"].as_array().unwrap().iter().any(|message| message["content"] == "Hello"));
assert!(!replay.to_string().contains("Later separate request"));
response(&mut socket).await;
fixture.drained().await;
let duplicate = fixture.mutation(2, "resume", input.clone(), operation).await.unwrap();
assert_eq!(duplicate, resumed, "operation journal returns original reply");
let second = fixture.mutation(2, "resume", input.clone(), Uuid::new_v4()).await.unwrap();
assert_eq!(second, json!({"kind":"parked","reason":"already_continued"}));
let selected = fixture.selection().await;
assert!(matches!(fixture.config.remove_connection(RemoveCatalogConnectionInput {
expected: ConnectionVersionBasis { connection_id: selected.connection_id, revision: selected.revision },
}).await.unwrap(), CatalogMutationResult::Committed { .. }));
fixture.store.set_session_archived::<SessionConfiguration>(&session, true, now()).await.unwrap();
std::fs::rename(&fixture.workspace, fixture.workspace.with_extension("removed")).unwrap();
let accepted = fixture.owner.resume(serde_json::from_value(input).unwrap(), &reference).await.unwrap();
let accepted = serde_json::to_value(accepted).unwrap();
assert_eq!(accepted["execution"]["invocationId"], execution["invocationId"]);
assert_eq!(accepted["execution"]["state"], "completed");
let delivery = fixture.query("work",json!({"sessionId":session,"target":{"kind":"message","messageId":message}})).await.unwrap();
assert_eq!(delivery["kind"],"delivered");
assert_eq!(delivery["shared"],false);
assert_eq!(delivery["execution"],accepted["execution"],"message follows canonical continuation, never later unrelated work");
let original_work = fixture.query("work",json!({"sessionId":session,"target":{"kind":"invocation","invocationId":source["invocationId"]}})).await.unwrap();
assert_eq!(original_work,delivery,"direct admission follows the same explicit continuation");
assert_eq!(original_work["answer"]["text"],"Elegant response");
assert_eq!(fixture.query("work",json!({"sessionId":session,"target":{"kind":"invocation","invocationId":source["invocationId"]},"cursor":{"invocationId":source["invocationId"],"offset":9000}})).await.unwrap(),json!({"kind":"changed"}),"old cursor must be rejected before applying its offset to a new answer");
assert!(tokio::time::timeout(Duration::from_millis(30), listener.accept()).await.is_err(), "no second model request");
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
}).await.unwrap();
}
#[tokio::test]
async fn resume_rejects_stale_foreign_busy_and_completed_sources_without_new_work() {
tokio::time::timeout(Duration::from_secs(45), async {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let fixture = Fixture::new(
&format!("http://{}/v1", listener.local_addr().unwrap()),
None,
)
.await;
let session = fixture.create("Resume fences").await;
let source = cancelled(&fixture, &listener, &session).await;
let input = resume_input(&fixture, &source).await;
for (field, value, reason) in [
(
"sourceHighWater",
json!(source["highWater"].as_u64().unwrap() + 1),
"source_changed",
),
("sourceInvocationId", json!("missing"), "source_missing"),
(
"sessionId",
json!(fixture.create("Other").await),
"source_missing",
),
] {
let mut invalid = input.clone();
invalid[field] = value;
assert_eq!(
fixture
.mutation(2, "resume", invalid, Uuid::new_v4())
.await
.unwrap(),
json!({"kind":"parked","reason":reason})
);
}
let active = fixture.send(&session).await.unwrap();
let (mut socket, _) = listener.accept().await.unwrap();
request(&mut socket).await;
assert_eq!(
fixture
.mutation(2, "resume", input.clone(), Uuid::new_v4())
.await
.unwrap(),
json!({"kind":"parked","reason":"busy"})
);
response(&mut socket).await;
fixture.drained().await;
let completed = fixture
.query(
"execution",
json!({"sessionId":session,"invocationId":active["invocationId"]}),
)
.await
.unwrap();
let completed_input = resume_input(&fixture, &completed["execution"]).await;
assert_eq!(
fixture
.mutation(2, "resume", completed_input, Uuid::new_v4())
.await
.unwrap(),
json!({"kind":"parked","reason":"source_not_resumable"})
);
// Replacing the directory at the same path must not authorize replay.
std::fs::rename(
&fixture.workspace,
fixture.workspace.with_extension("original"),
)
.unwrap();
std::fs::create_dir(&fixture.workspace).unwrap();
assert_eq!(
fixture
.mutation(2, "resume", input, Uuid::new_v4())
.await
.unwrap(),
json!({"kind":"parked","reason":"workspace_changed"})
);
assert!(
tokio::time::timeout(Duration::from_millis(30), listener.accept())
.await
.is_err()
);
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
})
.await
.unwrap();
}
#[tokio::test]
async fn application_reactivation_resumes_sealed_source_through_original_engine() {
tokio::time::timeout(Duration::from_secs(45), async {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let mut fixture = Fixture::new(
&format!("http://{}/v1", listener.local_addr().unwrap()),
None,
)
.await;
let session = fixture.create("Resume after reactivation").await;
let source = cancelled(&fixture, &listener, &session).await;
let input = resume_input(&fixture, &source).await;
let deployment = fixture.app.source().unwrap().deployment;
fixture.app.shutdown().await.unwrap();
let captured = Arc::new(StdMutex::new(None));
let capture = captured.clone();
let mut native = NativeRegistry::default();
maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
native
.insert(
IMPLEMENTATION.into(),
Arc::new(move |context, configuration| {
let capture = capture.clone();
Box::pin(async move {
let owner = Owner::open(
context,
configuration,
crate::owner::tests::bundled_catalog(),
)
.await
.unwrap();
*capture.lock().unwrap() = Some(owner.clone());
let service = Arc::new(Agent(owner));
NativeActivation {
exports: Ok(BTreeMap::from([(
EXPORT.into(),
maka_plugin_core::runtime::Published {
contract: contract(),
value: service.clone()
as Arc<dyn maka_host_runtime::application::Handler>,
},
)])),
owner: service,
}
})
}),
)
.unwrap();
fixture.app = Application::start_from_store(
fixture.store.clone(),
deployment,
native,
fixture.issuer.clone(),
Arc::new(Grant(fixture.issuer.clone(), None)),
Some(maka_host_js::factory(Default::default())),
)
.await
.unwrap();
fixture.app.ready().await.unwrap();
fixture.owner = captured.lock().unwrap().take().unwrap();
let resumed = fixture
.mutation(2, "resume", input, Uuid::new_v4())
.await
.unwrap();
assert_eq!(resumed["kind"], "started", "{resumed}");
let (mut socket, _) = listener.accept().await.unwrap();
assert!(
request(&mut socket).await["messages"]
.as_array()
.unwrap()
.iter()
.any(|message| message["content"] == "Hello")
);
response(&mut socket).await;
fixture.drained().await;
let execution = fixture
.query(
"execution",
json!({"sessionId":session,"invocationId":resumed["execution"]["invocationId"]}),
)
.await
.unwrap();
assert_eq!(execution["execution"]["state"], "completed");
let old = fixture
.query(
"execution",
json!({"sessionId":session,"invocationId":source["invocationId"]}),
)
.await
.unwrap();
assert_eq!(old["execution"], source);
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
})
.await
.unwrap();
}
#[tokio::test]
async fn resume_refuses_unsettled_effect_outside_its_selected_lineage() {
use maka_agent_facts::event::EventWrite;
use maka_agent_facts::event::Fact;
use maka_agent_facts::event::InvocationOutcome;
use maka_agent_facts::event::RuntimeEvent;
use maka_agent_facts::input::InvocationInput;
tokio::time::timeout(Duration::from_secs(45), async {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let fixture = Fixture::new(
&format!("http://{}/v1", listener.local_addr().unwrap()),
None,
)
.await;
let session = fixture.create("Unresolved prior effect").await;
let source = cancelled(&fixture, &listener, &session).await;
// Canonical crash-cut fixture: a later sealed invocation has a dispatched
// effect without a settlement. No production recovery override is used.
let invocation = Invocation {
session_id: session.clone(),
turn_id: "later-turn".into(),
run_id: "later-run".into(),
invocation_id: "later-invocation".into(),
};
for fact in [
Fact::InvocationOpened {
configuration: None,
input: InvocationInput::Message {
content: "Unresolved later work".into(),
source_messages: vec![],
request_fingerprint: None,
},
},
Fact::ToolDispatched {
presentation: None,
title: None,
operation_id: "unresolved".into(),
call: maka_agent_facts::tool_call::ToolCallIdentity::standalone(
"unresolved-call".into(),
),
name: "write".into(),
input: json!({}),
},
Fact::InvocationEnded {
outcome: InvocationOutcome::Failed {
model_failure: None,
class: "outcome_unknown".into(),
message: None,
},
},
] {
fixture
.store
.append(&EventWrite::plain(RuntimeEvent::new(invocation.clone(), fact)).unwrap())
.await
.unwrap();
}
let resumed = fixture
.mutation(
2,
"resume",
resume_input(&fixture, &source).await,
Uuid::new_v4(),
)
.await
.unwrap();
assert_eq!(
resumed,
json!({"kind":"parked","reason":"safety_check_failed"})
);
assert!(
tokio::time::timeout(Duration::from_millis(30), listener.accept())
.await
.is_err()
);
let facts = service_tools::facts(&fixture, &session).await;
assert_eq!(
facts
.iter()
.filter(|fact| matches!(fact, Fact::InvocationOpened { .. }))
.count(),
2
);
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
})
.await
.unwrap();
}
#[tokio::test]
async fn resume_uses_live_workspace_permissions_instead_of_original_grants() {
tokio::time::timeout(Duration::from_secs(45), async {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let fixture = Fixture::with_services(
&format!("http://{}/v1", listener.local_addr().unwrap()),
None,
None,
Some(service_tools::Setup::files("write")),
)
.await;
let session = fixture.create("Resume permission boundary").await;
let source = cancelled(&fixture, &listener, &session).await;
let changed = files::change(&fixture, &session, "read_only", false, true).await;
assert_eq!(changed["session"]["boundaryRevision"], 1);
let resumed = fixture
.mutation(
2,
"resume",
resume_input(&fixture, &source).await,
Uuid::new_v4(),
)
.await
.unwrap();
assert_eq!(resumed["kind"], "started", "{resumed}");
let (mut socket, _) = listener.accept().await.unwrap();
request(&mut socket).await;
files::tool_response(&mut socket, json!({"path":"blocked.txt","content":"never"})).await;
let (mut socket, _) = listener.accept().await.unwrap();
let sent = request(&mut socket).await;
let tool = sent["messages"]
.as_array()
.unwrap()
.iter()
.find(|message| message["role"] == "tool")
.unwrap();
let result: Value = serde_json::from_str(tool["content"].as_str().unwrap()).unwrap();
assert_eq!(result["kind"], "failed");
response(&mut socket).await;
fixture.drained().await;
assert!(!fixture.workspace.join("blocked.txt").exists());
assert!(
fixture
.store
.pending_interactions(&session)
.await
.unwrap()
.is_empty()
);
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
})
.await
.unwrap();
}
#[tokio::test]
async fn work_answer_pages_preserve_exact_unicode_nul_and_owner_after_later_work() {
tokio::time::timeout(Duration::from_secs(45), async {
let listener=TcpListener::bind("127.0.0.1:0").await.unwrap();
let fixture=Fixture::new(&format!("http://{}/v1",listener.local_addr().unwrap()),None).await;
let session=fixture.create("Original work answer").await;
let original=fixture.send(&session).await.unwrap();
let target=json!({"kind":"invocation","invocationId":original["invocationId"]});
let (mut socket,_)=listener.accept().await.unwrap();request(&mut socket).await;
let answer="中\0🦀\n".repeat(2200);
response_with_thinking(&mut socket,&answer,Some("Hidden worker reasoning")).await;
fixture.drained().await;
fixture.send(&session).await.unwrap();
let (mut socket,_)=listener.accept().await.unwrap();request(&mut socket).await;
response_with_text(&mut socket,"Unrelated later answer").await;fixture.drained().await;
let mut cursor=Value::Null;let mut joined=String::new();let mut pages=0;
loop {
let page=fixture.query("work",json!({"sessionId":session,"target":target,"cursor":cursor})).await.unwrap();
assert_eq!(page["kind"],"delivered");assert_eq!(page["execution"]["invocationId"],original["invocationId"]);
assert_eq!(page["execution"]["state"],"completed");assert_eq!(page["pendingInteractions"],0);
let text=page["answer"]["text"].as_str().unwrap();assert!(text.len()<=16_384);joined.push_str(text);pages+=1;
cursor=page["answer"]["next"].clone();if cursor.is_null() {break;}
assert_eq!(cursor["offset"],joined.len());
}
assert!(pages>1);assert_eq!(joined,answer);assert!(!joined.contains("Hidden"));
assert!(fixture.query("work",json!({"sessionId":session,"target":target,"cursor":{"invocationId":original["invocationId"],"offset":1}})).await.is_err());
let other=fixture.create("Different owner").await;
assert_eq!(fixture.query("work",json!({"sessionId":other,"target":target})).await.unwrap(),json!({"kind":"missing"}));
fixture.app.shutdown().await.unwrap();fixture.config.shutdown().await.unwrap();
}).await.unwrap();
}