diff options
Diffstat (limited to 'src/detect/manifest')
| -rw-r--r-- | src/detect/manifest/tests.rs | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/src/detect/manifest/tests.rs b/src/detect/manifest/tests.rs index c5522952..bdb0789d 100644 --- a/src/detect/manifest/tests.rs +++ b/src/detect/manifest/tests.rs @@ -290,6 +290,74 @@ fn detection_uses_cached_manifest_until_explicit_reload() { } #[test] +fn compiled_rules_are_shared_until_manifest_reload() { + with_manifest_dirs("shared-compiled-rules", || { + write_remote_codex(&format!( + "{}\nregex = ['^cached-[a-z]+$']\n", + remote_manifest("9999.01.01.1", "blocked", "cached-ready") + )); + let first = load_manifest(Agent::Codex).unwrap(); + let second = load_manifest(Agent::Codex).unwrap(); + assert!(!first.compiled_rules.is_empty()); + assert_eq!( + first.compiled_rules.as_ptr(), + second.compiled_rules.as_ptr(), + "cached loads must retain the same compiled rules and regex search caches" + ); + + write_remote_codex_without_reload(&format!( + "{}\nregex = ['^new-[a-z]+$']\n", + remote_manifest("9999.01.01.2", "working", "new-ready") + )); + let unchanged = load_manifest(Agent::Codex).unwrap(); + assert_eq!( + first.compiled_rules.as_ptr(), + unchanged.compiled_rules.as_ptr() + ); + + reload_manifests_for_agents(&[Agent::Codex]); + let reloaded = load_manifest(Agent::Codex).unwrap(); + let shared_reload = load_manifest(Agent::Codex).unwrap(); + assert_ne!( + first.compiled_rules.as_ptr(), + reloaded.compiled_rules.as_ptr() + ); + assert_eq!( + reloaded.compiled_rules.as_ptr(), + shared_reload.compiled_rules.as_ptr() + ); + assert!(compiled_rule_matches( + &first.compiled_rules[0], + "cached-ready" + )); + assert!(!compiled_rule_matches( + &first.compiled_rules[0], + "new-ready" + )); + assert_eq!( + explain(Agent::Codex, "new-ready").state, + AgentState::Working + ); + + std::thread::scope(|scope| { + for _ in 0..4 { + let reloaded = &reloaded; + scope.spawn(move || { + let loaded = load_manifest(Agent::Codex).unwrap(); + assert_eq!( + loaded.compiled_rules.as_ptr(), + reloaded.compiled_rules.as_ptr() + ); + for _ in 0..8 { + assert_eq!(detect(Agent::Codex, "new-ready").state, AgentState::Working); + } + }); + } + }); + }); +} + +#[test] fn all_bundled_manifests_parse_and_validate() { for agent in Agent::SCREEN_MANIFEST_AGENTS { assert!( @@ -1105,6 +1173,50 @@ fn codex_screen_working_fallback_handles_static_osc_title() { } #[test] +fn codex_screen_working_fallback_handles_activity_labels_and_queued_inputs() { + for prefix in ["", "• ", "◦ "] { + for label in ["Working", "Fixing bug in queue region"] { + for queue in [ + "", + "\n• Queued follow-up inputs\n ↳ Follow up after this turn\n alt + ↑ edit last queued message\n", + "\n• Messages to be submitted after next tool call\n (press esc to interrupt and send immediately)\n ↳ Keep waiting until the sleep finishes.\n", + "\n• Messages to be submitted after next tool call (press esc to interrupt and send immediately)\n ↳ Keep waiting until the sleep finishes.\n", + "\n• Messages to be submitted after next tool call (press esc to interrupt\n and send immediately)\n ↳ Keep waiting until the sleep finishes.\n", + "\n• Messages to be submitted after next\n tool call (press esc to interrupt and\n send immediately)\n ↳ Keep waiting until the sleep finishes.\n", + "\n• Messages to be submitted at end of turn\n ↳ Follow up after this turn\n", + "\n• Messages to be submitted after next tool call\n (press esc to interrupt and send immediately)\n ↳ Keep waiting until the sleep finishes.\n\n• Queued follow-up inputs\n ↳ After this turn reply ok.\n alt + ↑ edit last queued message\n", + ] { + let screen = format!( + "{prefix}{label} (1m 16s • esc to interrupt) · 1 background terminal running · /ps to view · /stop to close\n\ + {queue}\n› Ask Codex to do anything\n\n model · /work\n" + ); + let result = osc_explain(Agent::Codex, &screen, "project", ""); + + assert_eq!(result.state, AgentState::Working, "{screen}"); + assert_eq!( + result.matched_rule.as_ref().map(|rule| rule.id.as_str()), + Some("screen_working_fallback"), + "{screen}" + ); + assert!(result.visible_working); + } + } + } +} + +#[test] +fn codex_screen_working_fallback_uses_latest_activity_after_interruption() { + let screen = "■ Conversation interrupted\n\n\ + › Try again\n\n\ + Working (4s • esc to interrupt)\n\n\ + › Ask Codex to do anything\n\n model · /work\n"; + let result = osc_explain(Agent::Codex, screen, "project", ""); + + assert_eq!(result.state, AgentState::Working); + assert!(result.visible_working); +} + +#[test] fn codex_osc_working_remains_preferred_over_screen_fallback() { let screen = "• Working (4s • esc to interrupt)\n\n\ › Use /skills to list available skills\n\n\ @@ -1262,6 +1374,19 @@ fn codex_screen_working_fallback_ignores_stale_and_prompt_text() { " ◦ Working (1m 16s • esc to interrupt)\n\ › Use /skills to list available skills\n\ gpt-5.6-sol default · /work\n", + "Working (1m 16s • esc to interrupt)\n\ + • Finished the task\n\ + › Ask Codex to do anything\n", + "Working (1m 16s • esc to interrupt)\n\ + ■ Conversation interrupted\n\ + › Ask Codex to do anything\n", + "Working (1m 16s • esc to interrupt)\n\ + ─ Worked for 1m 16s ─\n\ + › Ask Codex to do anything\n", + "Working (1m 16s • esc to interrupt)\n•\nMessages to be submitted after next tool call\n› Ask Codex to do anything\n", + "› Explain this status:\n Working (1m 16s • esc to interrupt)\n", + "• Example (press esc to interrupt)\n\ + › Ask Codex to do anything\n", ]; for screen in screens { |
