diff --git a/bin/response-harvester.py b/bin/response-harvester.py index 0c04ea6..5e74102 100755 --- a/bin/response-harvester.py +++ b/bin/response-harvester.py @@ -108,6 +108,23 @@ def iter_verb_markers(text): yield m.group(1), m.group(2).strip() +REFUSAL_PATTERNS = [ + r"\bnot doing that\b", + r"\brefuse to\b", + r"\bdecline to\b", + r"\bdeclining\b", + r"\bwill not perform\b", + r"\bnot emitting the \[?result\]?\b", + r"\bnot sending result\b", + r"\bproduction change with recursion risk\b", +] + +def detect_explicit_refusal(text): + """Detect plain-language refusal/decline from an assistant in a task thread.""" + t = (text or "").lower() + return any(re.search(pat, t) for pat in REFUSAL_PATTERNS) + + def parse_tool_calls(text): """ Extract structured tool/exec calls from assistant messages. @@ -581,6 +598,28 @@ def process_messages(raw_messages, agent, thread_id, thread_name, last_wm, follo if author == "assistant": markers = list(iter_result_markers(text)) verbs = list(iter_verb_markers(text)) + + # Synthesize [RESULT ] DECLINE if assistant explicitly refuses the task in plain text + if not markers and not verbs and detect_explicit_refusal(text): + matching_jid = None + for f_id, f_rec in followups.items(): + if f_rec.get("status") in ("pending", "acknowledged") and (f_rec.get("thread_uuid") == thread_id or f_rec.get("recipient") == agent): + matching_jid = f_rec.get("job_id") or f_id + break + if not matching_jid and JOB_SIDECHATS_FILE.exists(): + try: + sc_state = json.loads(JOB_SIDECHATS_FILE.read_text(encoding="utf-8")) + for k, v in sc_state.items(): + if isinstance(v, dict) and v.get("thread_uuid") == thread_id: + matching_jid = k + break + except Exception: + pass + if matching_jid: + refusal_snippet = f"DECLINE: {text.strip()[:180]}" + print(f"[{agent}] Synthesizing [RESULT {matching_jid}] DECLINE from explicit assistant refusal") + markers.append((matching_jid, refusal_snippet)) + # Check for structured tool / command calls from the agent tool_calls = parse_tool_calls(text) if tool_calls and not dry_run: