diff --git a/scripts/build-backend-env b/scripts/build-backend-env index 26be877a3..2d93810e7 100755 --- a/scripts/build-backend-env +++ b/scripts/build-backend-env @@ -19,3 +19,78 @@ meerkat_selected_build_backend() { printf 'cargo\n' fi } + +meerkat_discover_local_secrets_env() { + local root dir parent + root="${1:-}" + if [[ -z "${root}" ]]; then + root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" + fi + + if [[ -n "${MEERKAT_SECRETS_ENV:-}" ]]; then + if [[ -f "${MEERKAT_SECRETS_ENV}" ]]; then + printf '%s\n' "${MEERKAT_SECRETS_ENV}" + else + echo "warning: MEERKAT_SECRETS_ENV points to a missing file: ${MEERKAT_SECRETS_ENV}" >&2 + fi + return 0 + fi + + dir="${root}" + while [[ -n "${dir}" && "${dir}" != "/" ]]; do + if [[ -f "${dir}/secrets.env" ]]; then + printf '%s\n' "${dir}/secrets.env" + return 0 + fi + if [[ -n "${HOME:-}" && "${dir}" == "${HOME}" ]]; then + return 0 + fi + parent="$(dirname "${dir}")" + if [[ "${parent}" == "${dir}" ]]; then + return 0 + fi + dir="${parent}" + done +} + +meerkat_secret_env_key_allowed() { + case "$1" in + ANTHROPIC_*|OPENAI_*|GEMINI_*|GOOGLE_*|RKAT_*|SMOKE_*|CLAUDE_CODE_OAUTH_TOKEN|BUILDBUDDY_API_KEY) + return 0 + ;; + *) + return 1 + ;; + esac +} + +meerkat_load_local_secrets_env() { + local root secrets_env loaded_env line key value + root="${1:-}" + secrets_env="$(meerkat_discover_local_secrets_env "${root}")" + if [[ -z "${secrets_env}" ]]; then + return 0 + fi + + if ! loaded_env="$( + set +u + set -a + # shellcheck disable=SC1090 + source "${secrets_env}" >/dev/null + env + )"; then + echo "warning: failed to load local secrets env from ${secrets_env}" >&2 + return 0 + fi + + while IFS= read -r line; do + key="${line%%=*}" + value="${line#*=}" + if [[ "${key}" == "${line}" ]]; then + continue + fi + if meerkat_secret_env_key_allowed "${key}" && [[ -z "${!key:-}" ]]; then + export "${key}=${value}" + fi + done <<< "${loaded_env}" +} diff --git a/scripts/buildbuddy-bazel-poc b/scripts/buildbuddy-bazel-poc index 3e47706d3..041455bbd 100755 --- a/scripts/buildbuddy-bazel-poc +++ b/scripts/buildbuddy-bazel-poc @@ -58,6 +58,10 @@ case "${1:-}" in ;; esac +workspace_root="$(git rev-parse --show-toplevel)" +source "${workspace_root}/scripts/build-backend-env" +meerkat_load_local_secrets_env "${workspace_root}" + if [[ -z "${BUILDBUDDY_API_KEY:-}" && -f "${HOME}/.zshrc" ]]; then key_line="$(grep -m1 -E '^[[:space:]]*(export[[:space:]]+)?BUILDBUDDY_API_KEY=' "${HOME}/.zshrc" || true)" if [[ -n "${key_line}" ]]; then @@ -83,8 +87,6 @@ if [[ -z "${BUILDBUDDY_API_KEY:-}" ]]; then exit 1 fi -workspace_root="$(git rev-parse --show-toplevel)" - if [[ -n "${BUILDBUDDY_BB:-}" ]]; then bb_bin="${BUILDBUDDY_BB}" elif command -v bb >/dev/null 2>&1; then @@ -258,7 +260,7 @@ case "${command}" in e2e-smoke-rbe) command="build" config="${BUILDBUDDY_BAZEL_CONFIG:-buildbuddy-macos-rbe}" - default_target="//tests/integration:e2e_smoke_lane_test //tests/integration:e2e_artifacts_bin //meerkat-cli:rkat //meerkat-cli:cli_mobpack_live_smoke_test //meerkat-cli:live_smoke_cli_test //meerkat-mcp-server:rkat_mcp_bin //meerkat-mob:smoke_mob_flow_runtime_test //meerkat-mob:smoke_mob_pictionary_test //meerkat-mob:smoke_mob_resume_test //meerkat-rest:rkat_rest_bin //meerkat-rpc:rkat_rpc_bin //meerkat-rpc:live_smoke_rpc_test //tests/integration:smoke_shared_realm_test" + default_target="@wasm_pack_darwin_arm64//:wasm-pack //tests/integration:e2e_smoke_lane_test //tests/integration:e2e_artifacts_bin //meerkat-cli:rkat //meerkat-cli:cli_mobpack_live_smoke_test //meerkat-cli:live_smoke_cli_test //meerkat-mcp-server:rkat_mcp_bin //meerkat-mob:smoke_mob_flow_runtime_test //meerkat-mob:smoke_mob_pictionary_test //meerkat-mob:smoke_mob_resume_test //meerkat-rest:rkat_rest_bin //meerkat-rpc:rkat_rpc_bin //meerkat-rpc:live_smoke_rpc_test //tests/integration:smoke_shared_realm_test" extra_args+=( --remote_download_outputs=toplevel --color=no diff --git a/scripts/buildbuddy-dev b/scripts/buildbuddy-dev index 16daae90f..e04fbe19a 100755 --- a/scripts/buildbuddy-dev +++ b/scripts/buildbuddy-dev @@ -41,6 +41,7 @@ EOF workspace_root="$(git rev-parse --show-toplevel)" cd "${workspace_root}" +source "${workspace_root}/scripts/build-backend-env" jobs="${BUILDBUDDY_JOBS:-64}" dry_run=0 @@ -96,6 +97,12 @@ lane="$1" shift extra_args=("$@") +case "${lane}" in + e2e-live|e2e-smoke) + meerkat_load_local_secrets_env "${workspace_root}" + ;; +esac + bb_config="${BUILDBUDDY_BAZEL_CONFIG:-buildbuddy-macos-rbe}" print_command() { diff --git a/scripts/e2e-smoke-lane b/scripts/e2e-smoke-lane index afe75e3fa..d41e3b90d 100755 --- a/scripts/e2e-smoke-lane +++ b/scripts/e2e-smoke-lane @@ -26,6 +26,9 @@ root="$(git rev-parse --show-toplevel)" cd "${root}" export WORKSPACE_ROOT="${root}" +source "${root}/scripts/build-backend-env" +meerkat_load_local_secrets_env "${root}" + buildbuddy_enabled() { case "${MEERKAT_BUILDBUDDY:-}" in 1|true|TRUE|yes|YES|on|ON|buildbuddy) @@ -75,11 +78,45 @@ buildbuddy_e2e_artifacts_bin() { return 1 } +buildbuddy_wasm_pack_bin() { + local output_base candidate + output_base="$(buildbuddy_output_base)" + for candidate in \ + "${output_base}/external/+http_archive+wasm_pack_darwin_arm64/wasm-pack" \ + "${output_base}/external/wasm_pack_darwin_arm64/wasm-pack" \ + "${output_base}/execroot/_main/external/+http_archive+wasm_pack_darwin_arm64/wasm-pack" \ + "${output_base}/execroot/_main/external/wasm_pack_darwin_arm64/wasm-pack"; do + if [[ -x "${candidate}" ]]; then + printf '%s\n' "${candidate}" + return 0 + fi + done + candidate="$( + find -L "${output_base}" \ + -type f \ + -name wasm-pack \ + -path '*/wasm_pack_darwin_arm64/*' \ + -perm -111 \ + 2>/dev/null | + head -n 1 + )" + if [[ -n "${candidate}" ]]; then + printf '%s\n' "${candidate}" + return 0 + fi + return 1 +} + e2e_artifacts_cmd=("${root}/scripts/repo-cargo" run -p meerkat-integration-tests --bin e2e_artifacts --) if buildbuddy_enabled; then if artifact_bin="$(buildbuddy_e2e_artifacts_bin)"; then e2e_artifacts_cmd=("${artifact_bin}") export MEERKAT_E2E_BAZEL_BIN_DIR="$(cd "$(dirname "${artifact_bin}")/../.." && pwd)" + if [[ -z "${RKAT_WASM_PACK_BIN:-}" && -z "${WASM_PACK:-}" ]] && + wasm_pack_bin="$(buildbuddy_wasm_pack_bin)"; then + export RKAT_WASM_PACK_BIN="${wasm_pack_bin}" + export WASM_PACK="${wasm_pack_bin}" + fi else echo "error: BuildBuddy e2e-smoke foundation did not produce e2e_artifacts_bin" >&2 echo "Run MEERKAT_BUILDBUDDY=1 make e2e-smoke so scripts/buildbuddy-dev builds //tests/integration:e2e_artifacts_bin first." >&2 diff --git a/scripts/run-build-backend-lane b/scripts/run-build-backend-lane index ca336010b..acfafe10c 100755 --- a/scripts/run-build-backend-lane +++ b/scripts/run-build-backend-lane @@ -92,12 +92,14 @@ case "${lane}" in exec "${cargo_bin}" e2e-system ;; e2e-live) + meerkat_load_local_secrets_env "${root}" if meerkat_buildbuddy_enabled; then exec "${root}/scripts/buildbuddy-dev" e2e-live fi exec "${cargo_bin}" e2e-live ;; e2e-smoke) + meerkat_load_local_secrets_env "${root}" if meerkat_buildbuddy_enabled; then "${root}/scripts/buildbuddy-dev" e2e-smoke case "${BUILDBUDDY_DRY_RUN:-}" in diff --git a/sdks/python/tests/test_e2e_smoke.py b/sdks/python/tests/test_e2e_smoke.py index 8ad425ee6..79f0b6368 100644 --- a/sdks/python/tests/test_e2e_smoke.py +++ b/sdks/python/tests/test_e2e_smoke.py @@ -631,7 +631,7 @@ async def test_smoke_scenario_57_realtime_channel_session_exchange(): if include_scenario(58): @pytest.mark.asyncio - @requires_live_llm + @requires_openai_realtime async def test_smoke_scenario_58_realtime_member_channel_respawn_continuity(): async with live_client() as client: if not client.has_capability("mob"): @@ -760,7 +760,7 @@ async def test_smoke_scenario_58_realtime_member_channel_respawn_continuity(): if include_scenario(64): @pytest.mark.asyncio - @requires_mixed_llms + @requires_openai_realtime async def test_smoke_scenario_64_realtime_member_channel_model_switch_continuity(): async with live_client() as client: if not client.has_capability("mob"): @@ -878,6 +878,7 @@ async def test_smoke_scenario_64_realtime_member_channel_model_switch_continuity assert "py-member-post-switch-64" in final_preview assert "maple" in final_preview assert final_state.get("realtime_attachment_status") in { + "unattached", "binding_not_ready", "binding_ready", "replacement_pending", diff --git a/sdks/typescript/tests/e2e_smoke.test.mjs b/sdks/typescript/tests/e2e_smoke.test.mjs index 06098da35..f37301f8b 100644 --- a/sdks/typescript/tests/e2e_smoke.test.mjs +++ b/sdks/typescript/tests/e2e_smoke.test.mjs @@ -598,13 +598,13 @@ describe("Live Smoke: TypeScript SDK", { skip: !binaryPath }, () => { if (includeScenario(59)) { it( "Scenario 59: realtime channel session exchange through the packaged SDK", - { skip: !hasAnthropicKey() }, + { skip: !hasOpenAIKey() }, async () => { const scenario = "Scenario 59"; const client = await withStepTimeout( scenario, "connect client", - connectClient({ realmId: "env_default" }), + connectClient({ realmId: `ts-realtime-59-${Date.now()}-${process.pid}` }), ); const session = await withStepTimeout( diff --git a/sdks/web/scripts/build-wasm.mjs b/sdks/web/scripts/build-wasm.mjs index a4c4c4956..9f7935499 100644 --- a/sdks/web/scripts/build-wasm.mjs +++ b/sdks/web/scripts/build-wasm.mjs @@ -15,6 +15,8 @@ const OUT_DIR = path.join(SDK_DIR, "wasm"); const CRATE_DIR = path.resolve(SDK_DIR, "../../meerkat-web-runtime"); const WORKSPACE_DIR = path.resolve(SDK_DIR, "../.."); const CACHE_MANIFEST = path.join(OUT_DIR, ".meerkat-wasm-build.json"); +const WASM_PACK_BIN = + process.env.RKAT_WASM_PACK_BIN || process.env.WASM_PACK || "wasm-pack"; const REQUIRED_OUTPUTS = [ "meerkat_web_runtime.js", "meerkat_web_runtime_bg.wasm", @@ -174,7 +176,13 @@ async function runCapture(command, args, options = {}) { child.stderr.on("data", (chunk) => { stderr += chunk; }); - child.on("error", reject); + child.on("error", (error) => { + reject( + new Error( + `failed to run ${command}: ${error.message}. Install wasm-pack or set RKAT_WASM_PACK_BIN/WASM_PACK to a wasm-pack binary.`, + ), + ); + }); child.on("exit", (code, signal) => { if (code === 0) { resolve(stdout); @@ -264,7 +272,7 @@ async function computeSourceHash() { hash.update("meerkat-web-runtime-wasm-v1\n"); hash.update(`rustflags=--cfg getrandom_backend="wasm_js"\n`); hash.update(`profile=${BUILD_PROFILE}\n`); - hash.update(`wasm-pack=${(await runCapture("wasm-pack", ["--version"])).trim()}\n`); + hash.update(`wasm-pack=${(await runCapture(WASM_PACK_BIN, ["--version"])).trim()}\n`); const inputs = await localCargoGraphInputs(); for (const filePath of inputs) { const relativePath = path.relative(WORKSPACE_DIR, filePath); @@ -309,7 +317,7 @@ async function run() { const profileArgs = BUILD_PROFILE === "release" ? [] : [`--${BUILD_PROFILE}`]; const child = spawn( - "wasm-pack", + WASM_PACK_BIN, [ "build", CRATE_DIR, @@ -328,7 +336,13 @@ async function run() { }, }, ); - child.on("error", reject); + child.on("error", (error) => { + reject( + new Error( + `failed to run ${WASM_PACK_BIN}: ${error.message}. Install wasm-pack or set RKAT_WASM_PACK_BIN/WASM_PACK to a wasm-pack binary.`, + ), + ); + }); child.on("exit", (code, signal) => { if (code === 0) { resolve(); diff --git a/tests/integration/src/e2e_lanes.rs b/tests/integration/src/e2e_lanes.rs index aad0a10e7..926321918 100644 --- a/tests/integration/src/e2e_lanes.rs +++ b/tests/integration/src/e2e_lanes.rs @@ -3567,7 +3567,7 @@ fn scenario_spec(id: u16) -> Option<&'static Spec> { lane: Lane::Smoke, title: "Python SDK realtime channel session exchange", timeout_secs: 1500, - required_env: &[&["RKAT_ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY"]], + required_env: &[&["RKAT_OPENAI_API_KEY", "OPENAI_API_KEY"]], required_bins: &["python3", "cargo"], cwd: "sdks/python", env: &[("MEERKAT_BIN_PATH", "{cargo_target_dir}/debug/rkat-rpc")], @@ -3599,7 +3599,7 @@ fn scenario_spec(id: u16) -> Option<&'static Spec> { lane: Lane::Smoke, title: "Python SDK realtime member respawn continuity", timeout_secs: 1800, - required_env: &[&["RKAT_ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY"]], + required_env: &[&["RKAT_OPENAI_API_KEY", "OPENAI_API_KEY"]], required_bins: &["python3", "cargo"], cwd: "sdks/python", env: &[("MEERKAT_BIN_PATH", "{cargo_target_dir}/debug/rkat-rpc")], @@ -3631,7 +3631,7 @@ fn scenario_spec(id: u16) -> Option<&'static Spec> { lane: Lane::Smoke, title: "TypeScript SDK realtime channel session exchange", timeout_secs: 1500, - required_env: &[&["RKAT_ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY"]], + required_env: &[&["RKAT_OPENAI_API_KEY", "OPENAI_API_KEY"]], required_bins: &["node", "npm", "cargo"], cwd: "sdks/typescript", env: &[("MEERKAT_BIN_PATH", "{cargo_target_dir}/debug/rkat-rpc")], @@ -3812,10 +3812,7 @@ fn scenario_spec(id: u16) -> Option<&'static Spec> { lane: Lane::Smoke, title: "Python SDK realtime member model-switch continuity", timeout_secs: 1800, - required_env: &[ - &["RKAT_ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY"], - &["RKAT_OPENAI_API_KEY", "OPENAI_API_KEY"], - ], + required_env: &[&["RKAT_OPENAI_API_KEY", "OPENAI_API_KEY"]], required_bins: &["python3", "cargo"], cwd: "sdks/python", env: &[("MEERKAT_BIN_PATH", "{cargo_target_dir}/debug/rkat-rpc")], diff --git a/tests/integration/tests/smoke_shared_realm.rs b/tests/integration/tests/smoke_shared_realm.rs index 01ae416d1..7f726a37b 100644 --- a/tests/integration/tests/smoke_shared_realm.rs +++ b/tests/integration/tests/smoke_shared_realm.rs @@ -2665,6 +2665,14 @@ fn mob_stream_send_response_request_intent(event: &Value) -> Option { }) } +fn mob_stream_send_response_request_subject(event: &Value) -> Option { + mob_stream_tool_args_json(event).and_then(|args| { + args["result"]["request_subject"] + .as_str() + .map(ToString::to_string) + }) +} + fn mob_stream_send_response_target(event: &Value) -> Option { mob_stream_tool_args_json(event).and_then(|args| { args["to"] @@ -5696,8 +5704,8 @@ async fn e2e_scenario_71_rust_sdk_realtime_audio_mob_collaboration_roundtrip() // Transitional fixture note: // the current machine-owned PEER_RESPONSE_TERMINAL notice preserves // request_id + status + result, but it does not yet carry a first-class - // copy of the original request intent. For this deterministic public - // smoke we therefore include `request_intent` in the peer response + // copy of the original request params. For this deterministic public + // smoke we therefore include `request_intent` and `request_subject` in the peer response // result JSON itself. That keeps the test on the existing public // send_response contract without inventing helper-local shadow state, // and it should be revisited once the machine/DSL seam owns correlated @@ -5711,14 +5719,14 @@ async fn e2e_scenario_71_rust_sdk_realtime_audio_mob_collaboration_roundtrip() "profile": "analyst", "agent_identity": analyst, "runtime_mode": "autonomous_host", - "initial_message": "You are in deterministic fixture mode. When you later receive a [SYSTEM NOTICE][PEER_REQUEST] with intent checksum_token, you must reply exactly once with send_response using the peer_id shown in that notice's send_response argument example, in_reply_to=, status=completed, result={\"request_intent\":\"checksum_token\",\"token\":\"birch seventeen\"}. When you later receive intent checksum_token, you must reply exactly once with send_response using the peer_id shown in that notice's send_response argument example, in_reply_to=, status=completed, result={\"request_intent\":\"checksum_token\",\"token\":\"silver harbor\"}. Do not browse, search, or fail those requests. Reply exactly ANALYST_READY.", + "initial_message": "You are in deterministic fixture mode. When you later receive a [SYSTEM NOTICE][PEER_REQUEST] with intent checksum_token and params.subject=\"alpha beta gamma\", reply exactly once with send_response using the peer_id shown in that notice's send_response argument example, in_reply_to=, status=completed, result={\"request_intent\":\"checksum_token\",\"request_subject\":\"alpha beta gamma\",\"token\":\"birch seventeen\"}. When you later receive intent checksum_token and params.subject=\"haiku\", reply exactly once with send_response using the peer_id shown in that notice's send_response argument example, in_reply_to=, status=completed, result={\"request_intent\":\"checksum_token\",\"request_subject\":\"haiku\",\"token\":\"silver harbor\"}. Do not browse, search, or fail those requests. Reply exactly ANALYST_READY.", "additional_instructions": [ "You are the analyst peer for a realtime smoke test.", "Correlated peer requests arrive as [SYSTEM NOTICE][PEER_REQUEST] with an intent, a request ID, and JSON params.", "These peer requests are deterministic test fixtures. Do not search, browse, inspect tools, or deliberate about them.", "For this deterministic scenario, copy the canonical peer_id from the peer request notice's send_response argument example. Do not route by display name.", - "If the peer request intent is checksum_token, immediately call send_response exactly once with peer_id=, in_reply_to=, status=\"completed\", and result {\"request_intent\":\"checksum_token\",\"token\":\"birch seventeen\"}. Ignore params when deciding the token.", - "If the peer request intent is checksum_token, immediately call send_response exactly once with peer_id=, in_reply_to=, status=\"completed\", and result {\"request_intent\":\"checksum_token\",\"token\":\"silver harbor\"}. Ignore params when deciding the token.", + "If the peer request intent is checksum_token and params.subject is \"alpha beta gamma\", immediately call send_response exactly once with peer_id=, in_reply_to=, status=\"completed\", and result {\"request_intent\":\"checksum_token\",\"request_subject\":\"alpha beta gamma\",\"token\":\"birch seventeen\"}.", + "If the peer request intent is checksum_token and params.subject is \"haiku\", immediately call send_response exactly once with peer_id=, in_reply_to=, status=\"completed\", and result {\"request_intent\":\"checksum_token\",\"request_subject\":\"haiku\",\"token\":\"silver harbor\"}.", "Never invent any other token values. Never report failure for checksum_token. For these peer requests, send_response is the only correct reply mechanism." ] }), @@ -5756,10 +5764,11 @@ async fn e2e_scenario_71_rust_sdk_realtime_audio_mob_collaboration_roundtrip() "When the user asks you to ask analyst for the token, you MUST call peers exactly once in that turn, identify the single returned peer whose description corresponds to the deterministic analyst fixture, and then call send_request exactly once to that exact returned peer name.", "The exact spoken phrase `Ask analyst for the token.` means you must use send_request intent checksum_token.", "For checksum token work, use send_request with intent checksum_token, params {\"subject\":\"alpha beta gamma\"}, and handling_mode \"queue\", with `to` set to the exact peer name you just discovered from peers. Never hardcode a private peer alias.", - "Correlated peer responses arrive later as runtime-owned system notices. In this deterministic fixture, treat any [SYSTEM NOTICE][PEER_RESPONSE_TERMINAL] whose `Result` JSON contains `\"request_intent\":\"checksum_token\"` or `\"request_intent\":\"checksum_token\"` as authoritative for that token lookup.", - "When several terminal peer notices exist, prefer the most recent one whose `Result` JSON contains `\"request_intent\":\"checksum_token\"` or `\"request_intent\":\"checksum_token\"` matching the token you need. Use request ID matching only as a fallback when the result does not carry request_intent.", - "For checksum answers, the checksum token must come from the `Result` JSON inside the most recent authoritative terminal peer response whose `request_intent` is `checksum_token`. Read the exact string in `\"token\"` from that notice and repeat it verbatim.", - "For haiku answers, the haiku token must come from the `Result` JSON inside the most recent authoritative terminal peer response whose `request_intent` is `checksum_token`. Read the exact string in `\"token\"` from that notice and repeat it verbatim.", + "Correlated peer responses arrive later as runtime-owned system notices. In this deterministic fixture, treat any [SYSTEM NOTICE][PEER_RESPONSE_TERMINAL] whose `Result` JSON contains `\"request_intent\":\"checksum_token\"` and a `request_subject` as authoritative for that subject's token lookup.", + "When several terminal peer notices exist, prefer the most recent one whose `Result` JSON has `\"request_intent\":\"checksum_token\"` and the `request_subject` matching the token you need. Use request ID matching only as a fallback when the result does not carry request_subject.", + "When distinguishing the checksum token request from the haiku token request, use request_subject. Never use request_intent alone to choose between them.", + "For checksum answers, the checksum token must come from the `Result` JSON inside the most recent authoritative terminal peer response whose `request_intent` is `checksum_token` and `request_subject` is `alpha beta gamma`. Read the exact string in `\"token\"` from that notice and repeat it verbatim.", + "For haiku answers, the haiku token must come from the `Result` JSON inside the most recent authoritative terminal peer response whose `request_intent` is `checksum_token` and `request_subject` is `haiku`. Read the exact string in `\"token\"` from that notice and repeat it verbatim.", "A remembered codeword and a peer-response token are different facts. Never reuse the remembered codeword as the token, even if both are in context at once.", "The placeholder text ``, ``, and `` is specification shorthand only. Never say angle brackets, placeholder words, or stand-ins like `checksum token` out loud. Replace them with the exact remembered codeword or the exact token from the authoritative peer response before answering.", "The exact spoken phrase `Ask analyst for the token.` is an asynchronous request turn. In that turn, after calling peers and send_request, answer with exactly `Waiting for analyst token.` and nothing else.", @@ -6123,6 +6132,19 @@ async fn e2e_scenario_71_rust_sdk_realtime_audio_mob_collaboration_roundtrip() ) .into()); } + let checksum_request_subject = + mob_stream_send_response_request_subject(&analyst_checksum_response_requested) + .ok_or_else(|| { + format!( + "analyst send_response did not include result.request_subject: {analyst_checksum_response_requested}" + ) + })?; + if checksum_request_subject != "alpha beta gamma" { + return Err(format!( + "analyst send_response carried unexpected request_subject `{checksum_request_subject}`: {analyst_checksum_response_requested}" + ) + .into()); + } let checksum_response_target = mob_stream_send_response_target(&analyst_checksum_response_requested).ok_or_else( || { @@ -6690,6 +6712,19 @@ turn45_output_text={:?}; turn45_frame_log={:?}; error={err}", ) .into()); } + let haiku_request_subject = + mob_stream_send_response_request_subject(&analyst_haiku_response_requested) + .ok_or_else(|| { + format!( + "analyst haiku send_response did not include result.request_subject: {analyst_haiku_response_requested}" + ) + })?; + if haiku_request_subject != "haiku" { + return Err(format!( + "analyst haiku send_response carried unexpected request_subject `{haiku_request_subject}`: {analyst_haiku_response_requested}" + ) + .into()); + } let haiku_response_target = mob_stream_send_response_target(&analyst_haiku_response_requested).ok_or_else( || { @@ -7279,7 +7314,7 @@ async fn e2e_scenario_72_rust_sdk_realtime_audio_member_model_switch_continuity( assert!( matches!( initial_status["realtime_attachment_status"].as_str(), - Some("binding_not_ready" | "binding_ready" | "replacement_pending" | "reattach_required") + Some("unattached" | "binding_not_ready" | "binding_ready" | "replacement_pending" | "reattach_required") ), "scenario 72 channel entered an unexpected status before the switch: {initial_status}" ); @@ -7410,7 +7445,7 @@ async fn e2e_scenario_72_rust_sdk_realtime_audio_member_model_switch_continuity( assert!( matches!( final_status["realtime_attachment_status"].as_str(), - Some("binding_not_ready" | "binding_ready" | "replacement_pending" | "reattach_required") + Some("unattached" | "binding_not_ready" | "binding_ready" | "replacement_pending" | "reattach_required") ), "scenario 72 channel entered an unexpected terminal member status: {final_status}" );