Add bidirectional RF handoff coordination
This commit is contained in:
@@ -17,6 +17,12 @@ from typing import Callable, Protocol
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from tools.rf_handoff_channel import GitResultChannel, load_result # noqa: E402
|
||||
|
||||
|
||||
DEFAULT_TASK_PATH = "docs/handoff/NEXT_RX_TASK.md"
|
||||
DEFAULT_STATE_PATH = ROOT / ".local/rx_agent_state.json"
|
||||
DEFAULT_OUTBOX = ROOT / ".local/rx_agent_outbox"
|
||||
@@ -71,6 +77,7 @@ class WatcherState:
|
||||
|
||||
last_seen_task_id: str | None = None
|
||||
last_completed_task_id: str | None = None
|
||||
last_approved_task_id: str | None = None
|
||||
deferred_task_id: str | None = None
|
||||
running: bool = False
|
||||
pid: int | None = None
|
||||
@@ -404,6 +411,7 @@ def handle_task(
|
||||
if decision != ApprovalDecision.APPROVE:
|
||||
return "APPROVAL_BLOCKED", state
|
||||
state.deferred_task_id = None
|
||||
state.last_approved_task_id = task.task_id
|
||||
paths = executor.prepare(task, task_text, source_head, dry_run=dry_run)
|
||||
output_function(f"TASK PREPARED = {paths[0]}")
|
||||
return "PREPARED", state
|
||||
@@ -476,8 +484,58 @@ def mark_completed(state_path: Path, task_id: str) -> None:
|
||||
save_state(state_path, state)
|
||||
|
||||
|
||||
def publish_result(arguments: argparse.Namespace) -> int:
|
||||
"""Publish only the approved small RX result to an existing branch."""
|
||||
|
||||
repository = arguments.repository.resolve()
|
||||
task = parse_task((repository / DEFAULT_TASK_PATH).read_text(encoding="utf-8-sig"))
|
||||
state = load_state(arguments.state_file.resolve())
|
||||
result = load_result(arguments.publish_result.resolve())
|
||||
if task.task_id != result.task_id:
|
||||
raise ValueError("result TASK_ID disagrees with current RX task")
|
||||
if task.allow_push != "RX_RESULT_ONLY" or task.rf_tx:
|
||||
raise ValueError("current task does not allow RX result publication")
|
||||
if state.last_approved_task_id != result.task_id:
|
||||
raise ValueError("current RX task has not passed the local approval gate")
|
||||
|
||||
def git_output(arguments: list[str]) -> str:
|
||||
return subprocess.run(
|
||||
["git", *arguments],
|
||||
cwd=repository,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
check=True,
|
||||
).stdout.strip()
|
||||
|
||||
branch = git_output(["branch", "--show-current"])
|
||||
if branch != task.source_branch:
|
||||
raise ValueError("current branch disagrees with task SOURCE_BRANCH")
|
||||
if git_output(["status", "--porcelain", "--untracked-files=no"]):
|
||||
raise ValueError("tracked worktree must be clean before result publication")
|
||||
head = git_output(["rev-parse", "HEAD"])
|
||||
remote_head = git_output(["rev-parse", f"{arguments.remote}/{task.source_branch}"])
|
||||
if head != remote_head:
|
||||
raise ValueError("current HEAD disagrees with the remote task branch")
|
||||
if result.source_head != head:
|
||||
raise ValueError("result SOURCE_HEAD disagrees with current checkout")
|
||||
channel = GitResultChannel(
|
||||
repository,
|
||||
remote=arguments.remote,
|
||||
branch=arguments.result_branch,
|
||||
)
|
||||
outcome = channel.publish(result)
|
||||
print(f"RX RESULT PUBLISH = {outcome.status}")
|
||||
print(f"RX RESULT HEAD = {outcome.commit}")
|
||||
print("RF TX = 0")
|
||||
return 0
|
||||
|
||||
|
||||
def run_watcher(arguments: argparse.Namespace) -> int:
|
||||
state_path = arguments.state_file.resolve()
|
||||
if arguments.publish_result is not None:
|
||||
return publish_result(arguments)
|
||||
if arguments.request_stop:
|
||||
stopped = request_stop(state_path, arguments.stop_timeout)
|
||||
print("WATCHER STOPPED" if stopped else "WATCHER STOP TIMEOUT")
|
||||
@@ -576,6 +634,8 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--request-stop", action="store_true")
|
||||
parser.add_argument("--stop-timeout", type=float, default=35.0)
|
||||
parser.add_argument("--mark-completed")
|
||||
parser.add_argument("--publish-result", type=Path)
|
||||
parser.add_argument("--result-branch", default="rx-node")
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user