From 47149eee8412e9724ef20734f48aef3b290d743f Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 12 Feb 2026 11:52:00 +0000 Subject: [PATCH] debug: view mem status line --- MODULE.bazel.lock | 4 +- codex-rs/core/src/codex.rs | 10 ++- codex-rs/core/src/memories/phase1.rs | 63 ++++++++++++- codex-rs/core/src/memories/phase2.rs | 88 ++++++++++++++++++- codex-rs/core/src/memories/start.rs | 39 +++++++- codex-rs/core/src/memories/tests.rs | 16 ++-- .../tui/src/bottom_pane/status_line_setup.rs | 7 ++ codex-rs/tui/src/chatwidget.rs | 23 +++++ codex-rs/tui/src/chatwidget/tests.rs | 35 ++++++++ 9 files changed, 265 insertions(+), 20 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 98efd3cede..6f15063691 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1154,8 +1154,8 @@ "reqwest_0.12.28": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"brotli_crate\",\"package\":\"brotli\",\"req\":\"^8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"name\":\"cookie_crate\",\"optional\":true,\"package\":\"cookie\",\"req\":\"^0.18.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"cookie_store\",\"optional\":true,\"req\":\"^0.22.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.28\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.28\"},{\"default_features\":false,\"features\":[\"std\",\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.28\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3\",\"optional\":true,\"req\":\"^0.0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3-quinn\",\"optional\":true,\"req\":\"^0.0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"tokio\"],\"name\":\"hickory-resolver\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http\",\"req\":\"^1.1\"},{\"name\":\"http-body\",\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.2\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\"],\"name\":\"hyper\",\"req\":\"^1.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"http2\",\"client\",\"server\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"tls12\"],\"name\":\"hyper-rustls\",\"optional\":true,\"req\":\"^0.27.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"hyper-tls\",\"optional\":true,\"req\":\"^0.6\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\",\"client-legacy\",\"client-proxy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"http2\",\"client\",\"client-legacy\",\"server-auto\",\"server-graceful\",\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"js-sys\",\"req\":\"^0.3.77\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0\"},{\"name\":\"log\",\"req\":\"^0.4.17\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"mime\",\"optional\":true,\"req\":\"^0.3.16\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0\"},{\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"^0.2.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.18\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"rustls\",\"runtime-tokio\"],\"name\":\"quinn\",\"optional\":true,\"req\":\"^0.11.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"std\",\"tls12\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"std\"],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"^1.9.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde_json\",\"req\":\"^1.0\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_urlencoded\",\"req\":\"^0.7.1\"},{\"features\":[\"futures\"],\"name\":\"sync_wrapper\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"net\",\"time\"],\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"tls12\"],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.26\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"io\"],\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7.9\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"retry\",\"timeout\",\"util\"],\"name\":\"tower\",\"req\":\"^0.5.2\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"limit\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"default_features\":false,\"features\":[\"follow-redirect\"],\"name\":\"tower-http\",\"req\":\"^0.6.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tower-service\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"url\",\"req\":\"^2.4\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"serde-serialize\"],\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-bindgen-futures\",\"req\":\"^0.4.18\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-streams\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"AbortController\",\"AbortSignal\",\"Headers\",\"Request\",\"RequestInit\",\"RequestMode\",\"Response\",\"Window\",\"FormData\",\"Blob\",\"BlobPropertyBag\",\"ServiceWorkerGlobalScope\",\"RequestCredentials\",\"File\",\"ReadableStream\",\"RequestCache\"],\"name\":\"web-sys\",\"req\":\"^0.3.28\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"zstd_crate\",\"package\":\"zstd\",\"req\":\"^0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"}],\"features\":{\"__rustls\":[\"dep:hyper-rustls\",\"dep:tokio-rustls\",\"dep:rustls\",\"__tls\"],\"__rustls-ring\":[\"hyper-rustls?/ring\",\"tokio-rustls?/ring\",\"rustls?/ring\",\"quinn?/ring\"],\"__tls\":[\"dep:rustls-pki-types\",\"tokio/io-util\"],\"blocking\":[\"dep:futures-channel\",\"futures-channel?/sink\",\"dep:futures-util\",\"futures-util?/io\",\"futures-util?/sink\",\"tokio/sync\"],\"brotli\":[\"tower-http/decompression-br\"],\"charset\":[\"dep:encoding_rs\",\"dep:mime\"],\"cookies\":[\"dep:cookie_crate\",\"dep:cookie_store\"],\"default\":[\"default-tls\",\"charset\",\"http2\",\"system-proxy\"],\"default-tls\":[\"dep:hyper-tls\",\"dep:native-tls-crate\",\"__tls\",\"dep:tokio-native-tls\"],\"deflate\":[\"tower-http/decompression-deflate\"],\"gzip\":[\"tower-http/decompression-gzip\"],\"hickory-dns\":[\"dep:hickory-resolver\",\"dep:once_cell\"],\"http2\":[\"h2\",\"hyper/http2\",\"hyper-util/http2\",\"hyper-rustls?/http2\"],\"http3\":[\"rustls-tls-manual-roots\",\"dep:h3\",\"dep:h3-quinn\",\"dep:quinn\",\"tokio/macros\"],\"json\":[\"dep:serde_json\"],\"macos-system-configuration\":[\"system-proxy\"],\"multipart\":[\"dep:mime_guess\",\"dep:futures-util\"],\"native-tls\":[\"default-tls\"],\"native-tls-alpn\":[\"native-tls\",\"native-tls-crate?/alpn\",\"hyper-tls?/alpn\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate?/vendored\"],\"rustls-tls\":[\"rustls-tls-webpki-roots\"],\"rustls-tls-manual-roots\":[\"rustls-tls-manual-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-manual-roots-no-provider\":[\"__rustls\"],\"rustls-tls-native-roots\":[\"rustls-tls-native-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-native-roots-no-provider\":[\"dep:rustls-native-certs\",\"hyper-rustls?/native-tokio\",\"__rustls\"],\"rustls-tls-no-provider\":[\"rustls-tls-manual-roots-no-provider\"],\"rustls-tls-webpki-roots\":[\"rustls-tls-webpki-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-webpki-roots-no-provider\":[\"dep:webpki-roots\",\"hyper-rustls?/webpki-tokio\",\"__rustls\"],\"socks\":[],\"stream\":[\"tokio/fs\",\"dep:futures-util\",\"dep:tokio-util\",\"dep:wasm-streams\"],\"system-proxy\":[\"hyper-util/client-proxy-system\"],\"trust-dns\":[],\"zstd\":[\"tower-http/decompression-zstd\"]}}", "resolv-conf_0.7.6": "{\"dependencies\":[],\"features\":{\"system\":[]}}", "ring_0.17.14": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.2.8\"},{\"default_features\":false,\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"getrandom\",\"req\":\"^0.2.10\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.148\",\"target\":\"cfg(all(any(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), all(target_arch = \\\"arm\\\", target_endian = \\\"little\\\")), any(target_os = \\\"android\\\", target_os = \\\"linux\\\")))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.155\",\"target\":\"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_vendor = \\\"apple\\\", any(target_os = \\\"ios\\\", target_os = \\\"macos\\\", target_os = \\\"tvos\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\")))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.148\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.37\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\"^0.52\",\"target\":\"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_os = \\\"windows\\\"))\"}],\"features\":{\"alloc\":[],\"default\":[\"alloc\",\"dev_urandom_fallback\"],\"dev_urandom_fallback\":[],\"less-safe-getrandom-custom-or-rdrand\":[],\"less-safe-getrandom-espidf\":[],\"slow_tests\":[],\"std\":[\"alloc\"],\"test_logging\":[],\"unstable-testing-arm-no-hw\":[],\"unstable-testing-arm-no-neon\":[],\"wasm32_unknown_unknown_js\":[\"getrandom/js\"]}}", - "rmcp-macros_0.14.0": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.23\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", - "rmcp_0.14.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"async-trait\",\"req\":\"^0.1.89\"},{\"kind\":\"dev\",\"name\":\"async-trait\",\"req\":\"^0.1\"},{\"name\":\"axum\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"serde\",\"clock\",\"std\",\"oldtime\"],\"name\":\"chrono\",\"req\":\"^0.4.38\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"features\":[\"serde\"],\"name\":\"chrono\",\"req\":\"^0.4.38\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"http\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"http-body\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"reqwest\"],\"name\":\"oauth2\",\"optional\":true,\"req\":\"^5.0\"},{\"name\":\"pastey\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"features\":[\"tokio1\"],\"name\":\"process-wrap\",\"optional\":true,\"req\":\"^9.0\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"json\",\"stream\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"rmcp-macros\",\"optional\":true,\"req\":\"^0.14.0\"},{\"features\":[\"chrono04\"],\"name\":\"schemars\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"chrono04\"],\"kind\":\"dev\",\"name\":\"schemars\",\"req\":\"^1.1.0\"},{\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"sse-stream\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"sync\",\"macros\",\"rt\",\"time\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"env-filter\",\"std\",\"fmt\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.4\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"__reqwest\":[\"dep:reqwest\"],\"auth\":[\"dep:oauth2\",\"__reqwest\",\"dep:url\"],\"client\":[\"dep:tokio-stream\"],\"client-side-sse\":[\"dep:sse-stream\",\"dep:http\"],\"default\":[\"base64\",\"macros\",\"server\"],\"elicitation\":[],\"macros\":[\"dep:rmcp-macros\",\"dep:pastey\"],\"reqwest\":[\"__reqwest\",\"reqwest?/rustls-tls\"],\"reqwest-tls-no-provider\":[\"__reqwest\",\"reqwest?/rustls-tls-no-provider\"],\"schemars\":[\"dep:schemars\"],\"server\":[\"transport-async-rw\",\"dep:schemars\"],\"server-side-http\":[\"uuid\",\"dep:rand\",\"dep:tokio-stream\",\"dep:http\",\"dep:http-body\",\"dep:http-body-util\",\"dep:bytes\",\"dep:sse-stream\",\"dep:axum\",\"tower\"],\"tower\":[\"dep:tower-service\"],\"transport-async-rw\":[\"tokio/io-util\",\"tokio-util/codec\"],\"transport-child-process\":[\"transport-async-rw\",\"tokio/process\",\"dep:process-wrap\"],\"transport-io\":[\"transport-async-rw\",\"tokio/io-std\"],\"transport-streamable-http-client\":[\"client-side-sse\",\"transport-worker\"],\"transport-streamable-http-client-reqwest\":[\"transport-streamable-http-client\",\"__reqwest\"],\"transport-streamable-http-server\":[\"transport-streamable-http-server-session\",\"server-side-http\",\"transport-worker\"],\"transport-streamable-http-server-session\":[\"transport-async-rw\",\"dep:tokio-stream\"],\"transport-worker\":[\"dep:tokio-stream\"]}}", + "rmcp-macros_0.15.0": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.23\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", + "rmcp_0.15.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"async-trait\",\"req\":\"^0.1.89\"},{\"kind\":\"dev\",\"name\":\"async-trait\",\"req\":\"^0.1\"},{\"name\":\"axum\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"serde\",\"clock\",\"std\",\"oldtime\"],\"name\":\"chrono\",\"req\":\"^0.4.38\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"features\":[\"serde\"],\"name\":\"chrono\",\"req\":\"^0.4.38\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"http\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"http-body\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"reqwest\"],\"name\":\"oauth2\",\"optional\":true,\"req\":\"^5.0\"},{\"name\":\"pastey\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"features\":[\"tokio1\"],\"name\":\"process-wrap\",\"optional\":true,\"req\":\"^9.0\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"json\",\"stream\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"rmcp-macros\",\"optional\":true,\"req\":\"^0.15.0\"},{\"features\":[\"chrono04\"],\"name\":\"schemars\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"chrono04\"],\"kind\":\"dev\",\"name\":\"schemars\",\"req\":\"^1.1.0\"},{\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"sse-stream\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"sync\",\"macros\",\"rt\",\"time\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"env-filter\",\"std\",\"fmt\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.4\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"__reqwest\":[\"dep:reqwest\"],\"auth\":[\"dep:oauth2\",\"__reqwest\",\"dep:url\"],\"client\":[\"dep:tokio-stream\"],\"client-side-sse\":[\"dep:sse-stream\",\"dep:http\"],\"default\":[\"base64\",\"macros\",\"server\"],\"elicitation\":[\"dep:url\"],\"macros\":[\"dep:rmcp-macros\",\"dep:pastey\"],\"reqwest\":[\"__reqwest\",\"reqwest?/rustls-tls\"],\"reqwest-native-tls\":[\"__reqwest\",\"reqwest?/native-tls\"],\"reqwest-tls-no-provider\":[\"__reqwest\",\"reqwest?/rustls-tls-no-provider\"],\"schemars\":[\"dep:schemars\"],\"server\":[\"transport-async-rw\",\"dep:schemars\",\"dep:pastey\"],\"server-side-http\":[\"uuid\",\"dep:rand\",\"dep:tokio-stream\",\"dep:http\",\"dep:http-body\",\"dep:http-body-util\",\"dep:bytes\",\"dep:sse-stream\",\"dep:axum\",\"tower\"],\"tower\":[\"dep:tower-service\"],\"transport-async-rw\":[\"tokio/io-util\",\"tokio-util/codec\"],\"transport-child-process\":[\"transport-async-rw\",\"tokio/process\",\"dep:process-wrap\"],\"transport-io\":[\"transport-async-rw\",\"tokio/io-std\"],\"transport-streamable-http-client\":[\"client-side-sse\",\"transport-worker\"],\"transport-streamable-http-client-reqwest\":[\"transport-streamable-http-client\",\"__reqwest\"],\"transport-streamable-http-server\":[\"transport-streamable-http-server-session\",\"server-side-http\",\"transport-worker\"],\"transport-streamable-http-server-session\":[\"transport-async-rw\",\"dep:tokio-stream\"],\"transport-worker\":[\"dep:tokio-stream\"]}}", "rsa_0.9.10": "{\"dependencies\":[{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"base64ct\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"const-oid\",\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"alloc\",\"oid\"],\"name\":\"digest\",\"req\":\"^0.10.5\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4.1\"},{\"default_features\":false,\"features\":[\"i128\",\"prime\",\"zeroize\"],\"name\":\"num-bigint\",\"package\":\"num-bigint-dig\",\"req\":\"^0.8.6\"},{\"default_features\":false,\"name\":\"num-integer\",\"req\":\"^0.1.39\"},{\"default_features\":false,\"features\":[\"libm\"],\"name\":\"num-traits\",\"req\":\"^0.2.9\"},{\"default_features\":false,\"features\":[\"alloc\",\"pkcs8\"],\"name\":\"pkcs1\",\"req\":\"^0.7.5\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"pkcs8\",\"req\":\"^0.10.2\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand_chacha\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"rand_core\",\"req\":\"^0.6.4\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.184\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.89\"},{\"default_features\":false,\"features\":[\"oid\"],\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10.5\"},{\"default_features\":false,\"features\":[\"oid\"],\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.10.5\"},{\"default_features\":false,\"features\":[\"oid\"],\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10.6\"},{\"default_features\":false,\"features\":[\"oid\"],\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10.6\"},{\"default_features\":false,\"features\":[\"oid\"],\"kind\":\"dev\",\"name\":\"sha3\",\"req\":\"^0.10.7\"},{\"default_features\":false,\"features\":[\"alloc\",\"digest\",\"rand_core\"],\"name\":\"signature\",\"req\":\">2.0, <2.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"spki\",\"req\":\"^0.7.3\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.1.1\"},{\"features\":[\"alloc\"],\"name\":\"zeroize\",\"req\":\"^1.5\"}],\"features\":{\"default\":[\"std\",\"pem\",\"u64_digit\"],\"getrandom\":[\"rand_core/getrandom\"],\"hazmat\":[],\"nightly\":[\"num-bigint/nightly\"],\"pem\":[\"pkcs1/pem\",\"pkcs8/pem\"],\"pkcs5\":[\"pkcs8/encryption\"],\"serde\":[\"dep:serde\",\"num-bigint/serde\"],\"std\":[\"digest/std\",\"pkcs1/std\",\"pkcs8/std\",\"rand_core/std\",\"signature/std\"],\"u64_digit\":[\"num-bigint/u64_digit\"]}}", "rust-embed-impl_8.11.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"rust-embed-utils\",\"req\":\"^8.11.0\"},{\"name\":\"shellexpand\",\"optional\":true,\"req\":\"^3\"},{\"default_features\":false,\"features\":[\"derive\",\"parsing\",\"proc-macro\",\"printing\"],\"name\":\"syn\",\"req\":\"^2\"},{\"name\":\"walkdir\",\"req\":\"^2.3.1\"}],\"features\":{\"compression\":[],\"debug-embed\":[],\"deterministic-timestamps\":[],\"include-exclude\":[\"rust-embed-utils/include-exclude\"],\"interpolate-folder-path\":[\"shellexpand\"],\"mime-guess\":[\"rust-embed-utils/mime-guess\"]}}", "rust-embed-utils_8.11.0": "{\"dependencies\":[{\"name\":\"globset\",\"optional\":true,\"req\":\"^0.4.8\"},{\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0.4\"},{\"name\":\"sha2\",\"req\":\"^0.10.5\"},{\"name\":\"walkdir\",\"req\":\"^2.3.1\"}],\"features\":{\"debug-embed\":[],\"include-exclude\":[\"globset\"],\"mime-guess\":[\"mime_guess\"]}}", diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 59cbf57dd5..27c6650b13 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1365,6 +1365,7 @@ impl Session { &sess, Arc::clone(&config), &session_configuration.session_source, + Some(sess.conversation_id.to_string()), ); Ok(sess) @@ -3743,10 +3744,15 @@ mod handlers { state.session_configuration.session_source.clone() }; - crate::memories::start_memories_startup_task(sess, Arc::clone(config), &session_source); + crate::memories::start_memories_startup_task( + sess, + Arc::clone(config), + &session_source, + Some(sub_id.clone()), + ); sess.send_event_raw(Event { - id: sub_id.clone(), + id: sub_id, msg: EventMsg::Warning(WarningEvent { message: "Memory update triggered.".to_string(), }), diff --git a/codex-rs/core/src/memories/phase1.rs b/codex-rs/core/src/memories/phase1.rs index 17e0359c98..cd8da80677 100644 --- a/codex-rs/core/src/memories/phase1.rs +++ b/codex-rs/core/src/memories/phase1.rs @@ -8,6 +8,7 @@ use crate::error::CodexErr; use crate::memories::metrics; use crate::memories::phase_one; use crate::memories::prompts::build_stage_one_input_message; +use crate::memories::start::emit_memory_progress; use crate::rollout::INTERACTIVE_SESSION_SOURCES; use crate::rollout::policy::should_persist_response_item_for_memories; use codex_api::ResponseEvent; @@ -27,6 +28,8 @@ use serde_json::Value; use serde_json::json; use std::path::Path; use std::sync::Arc; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; use tracing::info; use tracing::warn; @@ -79,17 +82,42 @@ struct StageOneOutput { /// 2) build one stage-1 request context /// 3) run stage-1 extraction jobs in parallel /// 4) emit metrics and logs -pub(in crate::memories) async fn run(session: &Arc, config: &Config) { +pub(in crate::memories) async fn run( + session: &Arc, + config: &Config, + progress_sub_id: &Option, +) { + emit_memory_progress( + session.as_ref(), + progress_sub_id, + "phase 1 scanning candidates", + ) + .await; + // 1. Claim startup job. let Some(claimed_candidates) = claim_startup_jobs(session, &config.memories).await else { return; }; + let claimed_count = claimed_candidates.len(); + emit_memory_progress( + session.as_ref(), + progress_sub_id, + format!("phase 1 running (0/{claimed_count} done)"), + ) + .await; + if claimed_candidates.is_empty() { session.services.otel_manager.counter( metrics::MEMORY_PHASE_ONE_JOBS, 1, &[("status", "skipped_no_candidates")], ); + emit_memory_progress( + session.as_ref(), + progress_sub_id, + "phase 1 complete (0/0 succeeded)", + ) + .await; return; } @@ -97,7 +125,14 @@ pub(in crate::memories) async fn run(session: &Arc, config: &Config) { let stage_one_context = build_request_context(session, config).await; // 3. Run the parallel sampling. - let outcomes = run_jobs(session, claimed_candidates, stage_one_context).await; + let outcomes = run_jobs( + session, + claimed_candidates, + stage_one_context, + progress_sub_id, + claimed_count, + ) + .await; // 4. Metrics and logs. let counts = aggregate_stats(outcomes); @@ -110,6 +145,13 @@ pub(in crate::memories) async fn run(session: &Arc, config: &Config) { counts.succeeded_no_output, counts.failed ); + let succeeded_count = counts.succeeded_with_output + counts.succeeded_no_output; + emit_memory_progress( + session.as_ref(), + progress_sub_id, + format!("phase 1 complete ({succeeded_count}/{claimed_count} succeeded)"), + ) + .await; } /// JSON schema used to constrain phase-1 model output. @@ -207,12 +249,27 @@ async fn run_jobs( session: &Arc, claimed_candidates: Vec, stage_one_context: RequestContext, + progress_sub_id: &Option, + claimed_count: usize, ) -> Vec { + let completed_count = Arc::new(AtomicUsize::new(0)); futures::stream::iter(claimed_candidates.into_iter()) .map(|claim| { let session = Arc::clone(session); let stage_one_context = stage_one_context.clone(); - async move { job::run(session.as_ref(), claim, &stage_one_context).await } + let progress_sub_id = progress_sub_id.clone(); + let completed_count = Arc::clone(&completed_count); + async move { + let result = job::run(session.as_ref(), claim, &stage_one_context).await; + let done = completed_count.fetch_add(1, Ordering::Relaxed) + 1; + emit_memory_progress( + session.as_ref(), + &progress_sub_id, + format!("phase 1 running ({done}/{claimed_count} done)"), + ) + .await; + result + } }) .buffer_unordered(phase_one::CONCURRENCY_LIMIT) .collect::>() diff --git a/codex-rs/core/src/memories/phase2.rs b/codex-rs/core/src/memories/phase2.rs index 28048498ce..753ba28344 100644 --- a/codex-rs/core/src/memories/phase2.rs +++ b/codex-rs/core/src/memories/phase2.rs @@ -6,6 +6,7 @@ use crate::memories::memory_root; use crate::memories::metrics; use crate::memories::phase_two; use crate::memories::prompts::build_consolidation_prompt; +use crate::memories::start::emit_memory_progress; use crate::memories::storage::rebuild_raw_memories_file_from_memories; use crate::memories::storage::sync_rollout_summaries_from_memories; use codex_config::Constrained; @@ -35,9 +36,18 @@ struct Counters { /// Runs memory phase 2 (aka consolidation) in strict order. The method represents the linear /// flow of the consolidation phase. -pub(super) async fn run(session: &Arc, config: Arc) { +pub(super) async fn run( + session: &Arc, + config: Arc, + progress_sub_id: &Option, +) { let Some(db) = session.services.state_db.as_deref() else { - // This should not happen. + emit_memory_progress( + session.as_ref(), + progress_sub_id, + "phase 2 skipped (state db unavailable)", + ) + .await; return; }; let root = memory_root(&config.codex_home); @@ -52,6 +62,12 @@ pub(super) async fn run(session: &Arc, config: Arc) { 1, &[("status", e)], ); + let progress = match e { + "skipped_not_dirty" => "phase 2 up to date", + "skipped_running" => "phase 2 already running", + _ => "phase 2 failed to claim global job", + }; + emit_memory_progress(session.as_ref(), progress_sub_id, progress).await; return; } }; @@ -61,6 +77,12 @@ pub(super) async fn run(session: &Arc, config: Arc) { // If we can't get the config, we can't consolidate. tracing::error!("failed to get agent config"); job::failed(session, db, &claim, "failed_sandbox_policy").await; + emit_memory_progress( + session.as_ref(), + progress_sub_id, + "phase 2 failed (sandbox policy rejected)", + ) + .await; return; }; @@ -70,6 +92,12 @@ pub(super) async fn run(session: &Arc, config: Arc) { Err(err) => { tracing::error!("failed to list stage1 outputs from global: {}", err); job::failed(session, db, &claim, "failed_load_stage1_outputs").await; + emit_memory_progress( + session.as_ref(), + progress_sub_id, + "phase 2 failed (could not load stage-1 outputs)", + ) + .await; return; } }; @@ -83,6 +111,12 @@ pub(super) async fn run(session: &Arc, config: Arc) { { tracing::error!("failed syncing local memory artifacts for global consolidation: {err}"); job::failed(session, db, &claim, "failed_sync_artifacts").await; + emit_memory_progress( + session.as_ref(), + progress_sub_id, + "phase 2 failed (could not sync local artifacts)", + ) + .await; return; } // [`raw_memories.md`] @@ -91,11 +125,23 @@ pub(super) async fn run(session: &Arc, config: Arc) { { tracing::error!("failed syncing local memory artifacts for global consolidation: {err}"); job::failed(session, db, &claim, "failed_rebuild_raw_memories").await; + emit_memory_progress( + session.as_ref(), + progress_sub_id, + "phase 2 failed (could not rebuild raw memories)", + ) + .await; return; } if raw_memories.is_empty() { // We check only after sync of the file system. job::succeed(session, db, &claim, new_watermark, "succeeded_no_input").await; + emit_memory_progress( + session.as_ref(), + progress_sub_id, + "phase 2 complete (no stage-1 outputs)", + ) + .await; return; } @@ -112,12 +158,25 @@ pub(super) async fn run(session: &Arc, config: Arc) { Err(err) => { tracing::error!("failed to spawn global memory consolidation agent: {err}"); job::failed(session, db, &claim, "failed_spawn_agent").await; + emit_memory_progress( + session.as_ref(), + progress_sub_id, + "phase 2 failed (could not spawn consolidation agent)", + ) + .await; return; } }; + emit_memory_progress(session.as_ref(), progress_sub_id, "phase 2 running").await; // 6. Spawn the agent handler. - agent::handle(session, claim, new_watermark, thread_id); + agent::handle( + session, + claim, + new_watermark, + thread_id, + progress_sub_id.clone(), + ); // 7. Metrics and logs. let counters = Counters { @@ -264,8 +323,18 @@ mod agent { claim: Claim, new_watermark: i64, thread_id: ThreadId, + progress_sub_id: Option, ) { let Some(db) = session.services.state_db.clone() else { + let session = Arc::clone(session); + tokio::spawn(async move { + emit_memory_progress( + session.as_ref(), + &progress_sub_id, + "phase 2 failed (state db unavailable)", + ) + .await; + }); return; }; let session = session.clone(); @@ -279,6 +348,12 @@ mod agent { Err(err) => { tracing::error!("agent_control.subscribe_status failed: {err:?}"); job::failed(&session, &db, &claim, "failed_subscribe_status").await; + emit_memory_progress( + session.as_ref(), + &progress_sub_id, + "phase 2 failed (status subscription unavailable)", + ) + .await; return; } }; @@ -298,6 +373,13 @@ mod agent { } else { job::failed(&session, &db, &claim, "failed_agent").await; } + let progress = match &final_status { + AgentStatus::Completed(_) => "phase 2 complete", + AgentStatus::Errored(_) | AgentStatus::NotFound => "phase 2 failed", + AgentStatus::Shutdown => "phase 2 cancelled", + AgentStatus::PendingInit | AgentStatus::Running => "phase 2 failed", + }; + emit_memory_progress(session.as_ref(), &progress_sub_id, progress).await; // Fire and forget close of the agent. if !matches!(final_status, AgentStatus::Shutdown | AgentStatus::NotFound) { diff --git a/codex-rs/core/src/memories/start.rs b/codex-rs/core/src/memories/start.rs index b93846857f..cd4a3f5f87 100644 --- a/codex-rs/core/src/memories/start.rs +++ b/codex-rs/core/src/memories/start.rs @@ -3,6 +3,9 @@ use crate::config::Config; use crate::features::Feature; use crate::memories::phase1; use crate::memories::phase2; +use codex_protocol::protocol::BackgroundEventEvent; +use codex_protocol::protocol::Event; +use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::SessionSource; use std::sync::Arc; use tracing::warn; @@ -15,6 +18,7 @@ pub(crate) fn start_memories_startup_task( session: &Arc, config: Arc, source: &SessionSource, + progress_sub_id: Option, ) { if config.ephemeral || !config.features.enabled(Feature::MemoryTool) @@ -25,6 +29,18 @@ pub(crate) fn start_memories_startup_task( if session.services.state_db.is_none() { warn!("state db unavailable for memories startup pipeline; skipping"); + let weak_session = Arc::downgrade(session); + tokio::spawn(async move { + let Some(session) = weak_session.upgrade() else { + return; + }; + emit_memory_progress( + session.as_ref(), + &progress_sub_id, + "phase 1 skipped (state db unavailable)", + ) + .await; + }); return; } @@ -35,8 +51,27 @@ pub(crate) fn start_memories_startup_task( }; // Run phase 1. - phase1::run(&session, &config).await; + phase1::run(&session, &config, &progress_sub_id).await; // Run phase 2. - phase2::run(&session, config).await; + phase2::run(&session, config, &progress_sub_id).await; }); } + +pub(super) async fn emit_memory_progress( + session: &Session, + progress_sub_id: &Option, + message: impl Into, +) { + let Some(sub_id) = progress_sub_id.as_ref() else { + return; + }; + + session + .send_event_raw(Event { + id: sub_id.clone(), + msg: EventMsg::BackgroundEvent(BackgroundEventEvent { + message: format!("memory startup: {}", message.into()), + }), + }) + .await; +} diff --git a/codex-rs/core/src/memories/tests.rs b/codex-rs/core/src/memories/tests.rs index 92fd69e4df..7fe675565e 100644 --- a/codex-rs/core/src/memories/tests.rs +++ b/codex-rs/core/src/memories/tests.rs @@ -368,7 +368,7 @@ mod phase2 { async fn dispatch_skips_when_global_job_is_not_dirty() { let harness = DispatchHarness::new().await; - phase2::run(&harness.session, Arc::clone(&harness.config)).await; + phase2::run(&harness.session, Arc::clone(&harness.config), &None).await; pretty_assertions::assert_eq!(harness.user_input_ops_count(), 0); let thread_ids = harness.manager.list_thread_ids().await; @@ -393,7 +393,7 @@ mod phase2 { "precondition should claim the running lock" ); - phase2::run(&harness.session, Arc::clone(&harness.config)).await; + phase2::run(&harness.session, Arc::clone(&harness.config), &None).await; let running_claim = harness .state_db @@ -421,7 +421,7 @@ mod phase2 { "stale lock precondition should be claimed" ); - phase2::run(&harness.session, Arc::clone(&harness.config)).await; + phase2::run(&harness.session, Arc::clone(&harness.config), &None).await; let running_claim = harness .state_db @@ -500,7 +500,7 @@ mod phase2 { .await .expect("enqueue global consolidation"); - phase2::run(&harness.session, Arc::clone(&harness.config)).await; + phase2::run(&harness.session, Arc::clone(&harness.config), &None).await; assert!( !tokio::fs::try_exists(&stale_summary_path) @@ -561,7 +561,7 @@ mod phase2 { constrained_config.permissions.sandbox_policy = Constrained::allow_only(SandboxPolicy::DangerFullAccess); - phase2::run(&harness.session, Arc::new(constrained_config)).await; + phase2::run(&harness.session, Arc::new(constrained_config), &None).await; let retry_claim = harness .state_db @@ -583,7 +583,7 @@ mod phase2 { .await .expect("create file at memory root"); - phase2::run(&harness.session, Arc::clone(&harness.config)).await; + phase2::run(&harness.session, Arc::clone(&harness.config), &None).await; let retry_claim = harness .state_db @@ -605,7 +605,7 @@ mod phase2 { .await .expect("create raw_memories.md as a directory"); - phase2::run(&harness.session, Arc::clone(&harness.config)).await; + phase2::run(&harness.session, Arc::clone(&harness.config), &None).await; let retry_claim = harness .state_db @@ -677,7 +677,7 @@ mod phase2 { "stage-1 success should enqueue global consolidation" ); - phase2::run(&session, Arc::clone(&config)).await; + phase2::run(&session, Arc::clone(&config), &None).await; let retry_claim = state_db .try_claim_global_phase2_job(ThreadId::new(), 3_600) diff --git a/codex-rs/tui/src/bottom_pane/status_line_setup.rs b/codex-rs/tui/src/bottom_pane/status_line_setup.rs index 29bcf7b9c9..7c80ca876c 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_setup.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_setup.rs @@ -91,6 +91,9 @@ pub(crate) enum StatusLineItem { /// Full session UUID. SessionId, + + /// Most recent memory startup pipeline progress update. + MemoryProgress, } impl StatusLineItem { @@ -124,6 +127,9 @@ impl StatusLineItem { StatusLineItem::SessionId => { "Current session identifier (omitted until session starts)" } + StatusLineItem::MemoryProgress => { + "Memory startup phase progress (omitted until available)" + } } } @@ -148,6 +154,7 @@ impl StatusLineItem { StatusLineItem::TotalInputTokens => "17,588 in", StatusLineItem::TotalOutputTokens => "265 out", StatusLineItem::SessionId => "019c19bd-ceb6-73b0-adc8-8ec0397b85cf", + StatusLineItem::MemoryProgress => "memory p2 running", } } } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index e36147ae73..72411cc565 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -612,6 +612,8 @@ pub(crate) struct ChatWidget { session_network_proxy: Option, // Shared latch so we only warn once about invalid status-line item IDs. status_line_invalid_items_warned: Arc, + // Most recent memory startup progress message (without prefix). + memory_progress_status: Option, // Cached git branch name for the status line (None if unknown). status_line_branch: Option, // CWD used to resolve the cached branch; change resets branch state. @@ -2109,11 +2111,28 @@ impl ChatWidget { fn on_background_event(&mut self, message: String) { debug!("BackgroundEvent: {message}"); + if let Some(progress) = Self::memory_progress_from_background_event(&message) { + self.memory_progress_status = Some(progress); + self.refresh_status_line(); + } self.bottom_pane.ensure_status_indicator(); self.bottom_pane.set_interrupt_hint_visible(true); self.set_status_header(message); } + fn memory_progress_from_background_event(message: &str) -> Option { + let (prefix, progress) = message.split_once(':')?; + if !prefix.trim().eq_ignore_ascii_case("memory startup") { + return None; + } + let progress = progress.trim(); + if progress.is_empty() { + None + } else { + Some(progress.to_string()) + } + } + fn on_undo_started(&mut self, event: UndoStartedEvent) { self.bottom_pane.ensure_status_indicator(); self.bottom_pane.set_interrupt_hint_visible(false); @@ -2656,6 +2675,7 @@ impl ChatWidget { current_cwd, session_network_proxy: None, status_line_invalid_items_warned, + memory_progress_status: None, status_line_branch: None, status_line_branch_cwd: None, status_line_branch_pending: false, @@ -2823,6 +2843,7 @@ impl ChatWidget { current_cwd, session_network_proxy: None, status_line_invalid_items_warned, + memory_progress_status: None, status_line_branch: None, status_line_branch_cwd: None, status_line_branch_pending: false, @@ -2979,6 +3000,7 @@ impl ChatWidget { current_cwd, session_network_proxy: None, status_line_invalid_items_warned, + memory_progress_status: None, status_line_branch: None, status_line_branch_cwd: None, status_line_branch_pending: false, @@ -4449,6 +4471,7 @@ impl ChatWidget { format_tokens_compact(self.status_line_total_usage().output_tokens) )), StatusLineItem::SessionId => self.thread_id.map(|id| id.to_string()), + StatusLineItem::MemoryProgress => self.memory_progress_status.clone(), } } diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index f0af50415f..5059b7b733 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1127,6 +1127,7 @@ async fn make_chatwidget_manual( current_cwd: None, session_network_proxy: None, status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)), + memory_progress_status: None, status_line_branch: None, status_line_branch_cwd: None, status_line_branch_pending: false, @@ -6123,6 +6124,40 @@ async fn status_line_branch_refreshes_after_interrupt() { assert!(chat.status_line_branch_pending); } +#[tokio::test] +async fn status_line_memory_progress_updates_from_background_events() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; + chat.config.tui_status_line = Some(vec!["memory-progress".to_string()]); + + chat.refresh_status_line(); + assert_eq!( + chat.status_line_value_for_item(&StatusLineItem::MemoryProgress), + None + ); + + chat.handle_codex_event(Event { + id: "bg-1".into(), + msg: EventMsg::BackgroundEvent(BackgroundEventEvent { + message: "memory startup: phase 1 running".to_string(), + }), + }); + assert_eq!( + chat.status_line_value_for_item(&StatusLineItem::MemoryProgress), + Some("phase 1 running".to_string()) + ); + + chat.handle_codex_event(Event { + id: "bg-2".into(), + msg: EventMsg::BackgroundEvent(BackgroundEventEvent { + message: "Waiting for `vim`".to_string(), + }), + }); + assert_eq!( + chat.status_line_value_for_item(&StatusLineItem::MemoryProgress), + Some("phase 1 running".to_string()) + ); +} + #[tokio::test] async fn stream_recovery_restores_previous_status_header() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;