Formalize the CLI as the first Channel extension (GLAS-WP-0003)
channels/base.py: GatewayInvocation dataclass + Channel ABC (parse_invocation/render_result), mirroring Rein one layer up -- a channel turns an external invocation into what run_task_through_rein needs, and turns its result back into whatever the invocation medium expects. channels/cli_channel.py: CLIChannel, a pure refactor of what cli.py did inline -- argparse Namespace -> GatewayInvocation, result dict -> json.dumps(..., indent=2). cli.py's run handler is now a thin construct-parse-invoke-render wrapper. docs/channel-contract.md documents the pattern and sketches (without building) a second channel. Found and fixed a real bug while live-testing this refactor: ReinAharness's default cli_bin was still "agent-harness", stale from before HARNESS-WP-0002-T02's rename. 4 new tests (27/27 passing), plus a real live run through python3 -m glas_harness.cli against rein-aharness/real Claude Code confirming identical output shape and a real commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
a87b924126
commit
d3130162b1
9 changed files with 251 additions and 17 deletions
0
src/glas_harness/channels/__init__.py
Normal file
0
src/glas_harness/channels/__init__.py
Normal file
38
src/glas_harness/channels/base.py
Normal file
38
src/glas_harness/channels/base.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
"""The channel contract — how an external invocation reaches the gateway.
|
||||
|
||||
See docs/channel-contract.md (GLAS-WP-0003). A Channel turns an external
|
||||
invocation (CLI args today; a Slack message or HTTP request for a future
|
||||
channel) into the inputs run_task_through_rein needs, and turns its
|
||||
result back into whatever the invocation medium expects.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class GatewayInvocation:
|
||||
"""Everything run_task_through_rein needs, channel-agnostic."""
|
||||
|
||||
sandbox_profile: str
|
||||
repo: str
|
||||
title: str
|
||||
description: str
|
||||
actor: str = "agt"
|
||||
project: str = "glas-harness"
|
||||
report_to_hub: bool = True
|
||||
|
||||
|
||||
class Channel(ABC):
|
||||
"""Base class for concrete invocation channels (CLI, Slack, HTTP, ...)."""
|
||||
|
||||
@abstractmethod
|
||||
def parse_invocation(self, raw: Any) -> GatewayInvocation:
|
||||
"""Turn a channel-specific invocation into a GatewayInvocation."""
|
||||
|
||||
@abstractmethod
|
||||
def render_result(self, result: dict[str, Any]) -> Any:
|
||||
"""Turn a gateway result dict into whatever this channel returns."""
|
||||
31
src/glas_harness/channels/cli_channel.py
Normal file
31
src/glas_harness/channels/cli_channel.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"""CLIChannel — the first Channel extension, formalizing what cli.py already did.
|
||||
|
||||
GLAS-WP-0003: this is a refactor, not a behavior change. cli.py's
|
||||
argparse Namespace -> GatewayInvocation mapping and the
|
||||
json.dumps(..., indent=2) output are exactly what cli.py did inline
|
||||
before this existed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from glas_harness.channels.base import Channel, GatewayInvocation
|
||||
|
||||
|
||||
class CLIChannel(Channel):
|
||||
def parse_invocation(self, raw: argparse.Namespace) -> GatewayInvocation:
|
||||
return GatewayInvocation(
|
||||
sandbox_profile=raw.sandbox_profile,
|
||||
repo=raw.repo,
|
||||
title=raw.title,
|
||||
description=raw.description,
|
||||
actor=raw.actor,
|
||||
project=raw.project,
|
||||
report_to_hub=not raw.no_hub,
|
||||
)
|
||||
|
||||
def render_result(self, result: dict[str, Any]) -> str:
|
||||
return json.dumps(result, indent=2)
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
"""glas-harness CLI — minimal gateway invocation (GLAS-WP-0001-T04)."""
|
||||
"""glas-harness CLI — thin wrapper around CLIChannel (GLAS-WP-0003)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
|
|
@ -23,18 +22,21 @@ def main(argv: list[str] | None = None) -> int:
|
|||
args = parser.parse_args(argv)
|
||||
|
||||
if args.command == "run":
|
||||
from glas_harness.channels.cli_channel import CLIChannel
|
||||
from glas_harness.gateway import run_task_through_rein
|
||||
|
||||
channel = CLIChannel()
|
||||
invocation = channel.parse_invocation(args)
|
||||
result = run_task_through_rein(
|
||||
sandbox_profile=args.sandbox_profile,
|
||||
repo=args.repo,
|
||||
title=args.title,
|
||||
description=args.description,
|
||||
actor=args.actor,
|
||||
project=args.project,
|
||||
report_to_hub=not args.no_hub,
|
||||
sandbox_profile=invocation.sandbox_profile,
|
||||
repo=invocation.repo,
|
||||
title=invocation.title,
|
||||
description=invocation.description,
|
||||
actor=invocation.actor,
|
||||
project=invocation.project,
|
||||
report_to_hub=invocation.report_to_hub,
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
print(channel.render_result(result))
|
||||
return 0 if result["tool_ok"] else 1
|
||||
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class ReinAharnessNotInstalled(RuntimeError):
|
|||
|
||||
|
||||
class ReinAharness(Rein):
|
||||
def __init__(self, cli_bin: str = "agent-harness", stream_tool_events: bool = False) -> None:
|
||||
def __init__(self, cli_bin: str = "rein-aharness", stream_tool_events: bool = False) -> None:
|
||||
self.cli_bin = cli_bin
|
||||
self.stream_tool_events = stream_tool_events
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue