feat(codex-bridge): notify origin on async completion

This commit is contained in:
wangrenzhu 2026-04-25 06:31:46 +08:00
parent f27b32d6b0
commit da25c6e163
8 changed files with 614 additions and 7 deletions

View file

@ -75,6 +75,19 @@ def test_start_task_uses_app_server_thread_turn_without_mailbox(tmp_path, monkey
assert "inbox" not in json.dumps(client.requests).lower()
def test_start_task_records_notify_target(tmp_path, monkeypatch):
manager = make_manager(tmp_path, monkeypatch)
result = manager.start_task("Analyze tests", cwd=str(tmp_path), notify_target="feishu:chat-1")
task_id = result["task"]["hermes_task_id"]
assert result["task"]["notify_target"] == "feishu:chat-1"
assert result["task"]["notification_status"] == "pending"
persisted = manager.status(task_id)["task"]
assert persisted["notify_target"] == "feishu:chat-1"
assert persisted["notification_status"] == "pending"
def test_server_approval_request_can_be_reported_and_resolved(tmp_path, monkeypatch):
manager = make_manager(tmp_path, monkeypatch)
started = manager.start_task("Run a safe command", cwd=str(tmp_path))
@ -143,8 +156,76 @@ def test_steer_and_interrupt_call_codex_turn_methods(tmp_path, monkeypatch):
assert client.requests[-1][0] == "turn/interrupt"
def test_notify_completed_sends_once_for_targeted_completed_task(tmp_path, monkeypatch):
manager = make_manager(tmp_path, monkeypatch)
started = manager.start_task("Summarize a bug", cwd=str(tmp_path), notify_target="feishu:chat-1")
task_id = started["task"]["hermes_task_id"]
deliveries = []
manager.record_event(
task_id,
"turn/completed",
{"turn": {"id": "turn-1", "status": "completed"}, "message": "Done fixing it."},
)
first = manager.notify_completed(notifier=lambda target, message: deliveries.append((target, message)) or {"ok": True})
second = manager.notify_completed(notifier=lambda target, message: deliveries.append((target, message)) or {"ok": True})
assert first["processed"] == 1
assert first["notifications"][0]["notification_status"] == "sent"
assert first["notifications"][0]["sent"] is True
assert second["processed"] == 0
assert len(deliveries) == 1
assert deliveries[0][0] == "feishu:chat-1"
assert task_id in deliveries[0][1]
assert manager.status(task_id)["task"]["notification_status"] == "sent"
def test_notify_completed_marks_no_target_without_sending(tmp_path, monkeypatch):
manager = make_manager(tmp_path, monkeypatch)
started = manager.start_task("No callback needed", cwd=str(tmp_path))
task_id = started["task"]["hermes_task_id"]
manager.record_event(
task_id,
"turn/completed",
{"turn": {"id": "turn-1", "status": "completed"}, "message": "Done."},
)
result = manager.notify_completed(notifier=lambda _target, _message: (_ for _ in ()).throw(AssertionError("sent")))
assert result["processed"] == 1
assert result["notifications"][0]["notification_status"] == "no_target"
assert result["notifications"][0]["sent"] is False
assert manager.status(task_id)["task"]["notification_status"] == "no_target"
def test_notify_completed_dry_run_does_not_send_or_mark(tmp_path, monkeypatch):
manager = make_manager(tmp_path, monkeypatch)
started = manager.start_task("Preview callback", cwd=str(tmp_path), notify_target="local")
task_id = started["task"]["hermes_task_id"]
manager.record_event(
task_id,
"turn/completed",
{"turn": {"id": "turn-1", "status": "completed"}, "message": "Done."},
)
result = manager.notify_completed(
dry_run=True,
notifier=lambda _target, _message: (_ for _ in ()).throw(AssertionError("sent")),
)
assert result["processed"] == 1
assert result["notifications"][0]["notification_status"] == "dry_run"
assert result["notifications"][0]["sent"] is False
assert manager.status(task_id)["task"]["notification_status"] == "pending"
def test_tool_schema_refuses_danger_full_access():
props = bridge.CODEX_BRIDGE_SCHEMA["parameters"]["properties"]
assert "danger-full-access" not in props["sandbox"]["enum"]
assert "never" not in props["approval_policy"]["enum"]
assert "notify_completed" in props["action"]["enum"]
assert "notify_target" in props