aboutsummaryrefslogtreecommitdiff
path: root/src/detect/manifest
diff options
context:
space:
mode:
Diffstat (limited to 'src/detect/manifest')
-rw-r--r--src/detect/manifest/tests.rs125
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 {