-
Notifications
You must be signed in to change notification settings - Fork 5.6k
feat(rss-twitter): migrate Playwright agent to v0.6 template layout #6839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
VasuBansal7576
wants to merge
4
commits into
aden-hive:main
Choose a base branch
from
VasuBansal7576:codex/rss-twitter-v06-clean
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
1e3eb7d
feat(rss-twitter): migrate Playwright agent to v0.6 template layout
VasuBansal7576 15680c3
fix: address rss twitter template review findings
VasuBansal7576 19ae3e5
fix: address additional rss review findings
VasuBansal7576 4931ff2
fix: validate rss max_articles input
VasuBansal7576 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # RSS-to-Twitter Agent (Playwright) | ||
|
|
||
| This template keeps the original behavior: | ||
|
|
||
| 1. Fetch RSS news | ||
| 2. Summarize with Ollama | ||
| 3. Ask you `y/n/q` per thread | ||
| 4. If `y`, auto-open Twitter/X and post via Playwright | ||
|
|
||
| Updated for Hive v0.6+ project layout and credential namespace support. | ||
|
|
||
| ## Run | ||
|
|
||
| From repo root: | ||
|
|
||
| ```bash | ||
| cd /Users/vasu/Desktop/hive | ||
| uv run python -m examples.templates.rss_twitter_agent run \ | ||
| --feed-url "https://news.ycombinator.com/rss" \ | ||
| --max-articles 3 | ||
| ``` | ||
|
|
||
| Optional credential ref (v0.6 format): | ||
|
|
||
| ```bash | ||
| uv run python -m examples.templates.rss_twitter_agent run \ | ||
| --feed-url "https://news.ycombinator.com/rss" \ | ||
| --max-articles 3 \ | ||
| --twitter-credential-ref twitter/default | ||
| ``` | ||
|
|
||
| ## Validate / Info | ||
|
|
||
| ```bash | ||
| uv run python -m examples.templates.rss_twitter_agent validate | ||
| uv run python -m examples.templates.rss_twitter_agent info | ||
| ``` | ||
|
|
||
| ## Ollama prerequisites | ||
|
|
||
| ```bash | ||
| ollama serve | ||
| ollama pull llama3.2 | ||
| ``` | ||
|
|
||
| ## Behavior notes | ||
|
|
||
| - First posting run opens browser login and stores session. | ||
| - Later runs reuse session automatically. | ||
| - You can override session path with `HIVE_TWITTER_SESSION_DIR`. | ||
| - Credential reference uses `{name}/{alias}` (example: `twitter/default`). | ||
| - Interactive review still uses per-thread `y/n/q` approval before posting. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| """RSS-to-Twitter Playwright agent (Hive v0.6-compatible package).""" | ||
|
|
||
| from .agent import RSSTwitterAgent, default_agent | ||
|
|
||
| __all__ = ["RSSTwitterAgent", "default_agent"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| """CLI for RSS-to-Twitter Playwright agent.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| import json | ||
| import sys | ||
|
|
||
| import click | ||
|
|
||
| from .agent import default_agent | ||
| from .run import run_interactive | ||
|
|
||
|
|
||
| @click.group() | ||
| @click.version_option(version="1.1.0") | ||
| def cli() -> None: | ||
| """RSS-to-Twitter Playwright agent.""" | ||
|
|
||
|
|
||
| @cli.command() | ||
| @click.option( | ||
| "--feed-url", default="https://news.ycombinator.com/rss", show_default=True | ||
| ) | ||
| @click.option("--max-articles", default=3, show_default=True, type=int) | ||
| @click.option( | ||
| "--twitter-credential-ref", | ||
| default=None, | ||
| help="Hive credential reference in {name}/{alias} format (example: twitter/default).", | ||
| ) | ||
| def run(feed_url: str, max_articles: int, twitter_credential_ref: str | None) -> None: | ||
| """Run the interactive RSS -> summarize -> approve -> post flow.""" | ||
| summary = asyncio.run( | ||
| run_interactive( | ||
| feed_url=feed_url, | ||
| max_articles=max_articles, | ||
| twitter_credential_ref=twitter_credential_ref, | ||
| ) | ||
| ) | ||
| click.echo(json.dumps(summary, indent=2, default=str)) | ||
| sys.exit(0) | ||
|
|
||
|
|
||
| @cli.command() | ||
| def validate() -> None: | ||
| """Validate basic graph structure metadata.""" | ||
| result = default_agent.validate() | ||
| if result["valid"]: | ||
| click.echo("Agent is valid") | ||
| return | ||
| click.echo("Agent has errors:") | ||
| for err in result["errors"]: | ||
| click.echo(f" ERROR: {err}") | ||
| sys.exit(1) | ||
|
|
||
|
|
||
| @cli.command() | ||
| @click.option("--json", "output_json", is_flag=True) | ||
| def info(output_json: bool) -> None: | ||
| """Show agent metadata.""" | ||
| data = default_agent.info() | ||
| if output_json: | ||
| click.echo(json.dumps(data, indent=2)) | ||
| return | ||
| click.echo(f"Agent: {data['name']}") | ||
| click.echo(f"Version: {data['version']}") | ||
| click.echo(f"Description: {data['description']}") | ||
| click.echo(f"Nodes: {', '.join(data['nodes'])}") | ||
| click.echo(f"Entry: {data['entry_node']}") | ||
| click.echo(f"Terminal: {', '.join(data['terminal_nodes'])}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| cli() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| """Agent metadata + simple execution wrapper for RSS-to-Twitter Playwright flow.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from framework.graph import Constraint, EdgeCondition, EdgeSpec, Goal, SuccessCriterion | ||
| from framework.graph.executor import ExecutionResult | ||
|
|
||
| from .config import metadata, validate_ollama | ||
| from .nodes import approve_node, fetch_node, generate_node, post_node, process_node | ||
| from .run import run_workflow | ||
|
|
||
|
|
||
| goal = Goal( | ||
| id="rss-to-twitter", | ||
| name="RSS-to-Twitter Content Repurposing", | ||
| description=( | ||
| "Fetch articles from RSS feeds, summarize them, generate engaging Twitter threads, " | ||
| "ask for explicit user approval, and post approved threads via Playwright." | ||
| ), | ||
| success_criteria=[ | ||
| SuccessCriterion( | ||
| id="feed-parsing", | ||
| description="Agent fetches and parses at least one feed item", | ||
| metric="article_count", | ||
| target=">=1", | ||
| weight=0.3, | ||
| ), | ||
| SuccessCriterion( | ||
| id="thread-quality", | ||
| description="Generated threads contain structured tweets with CTA and link", | ||
| metric="thread_count", | ||
| target=">=1", | ||
| weight=0.35, | ||
| ), | ||
| SuccessCriterion( | ||
| id="approval-gate", | ||
| description="User explicitly approves/rejects each thread", | ||
| metric="approval_present", | ||
| target="true", | ||
| weight=0.2, | ||
| ), | ||
| SuccessCriterion( | ||
| id="posting", | ||
| description="Approved threads are posted through Playwright", | ||
| metric="post_success", | ||
| target="true when approved", | ||
| weight=0.15, | ||
| ), | ||
| ], | ||
| constraints=[ | ||
| Constraint( | ||
| id="human-approval-required", | ||
| description="Posting requires explicit human y/n decision", | ||
| constraint_type="safety", | ||
| category="approval", | ||
| ), | ||
| Constraint( | ||
| id="source-attribution", | ||
| description="Threads should include source links", | ||
| constraint_type="quality", | ||
| category="content", | ||
| ), | ||
| ], | ||
| ) | ||
|
|
||
| nodes = [fetch_node, process_node, generate_node, approve_node, post_node] | ||
| edges = [ | ||
| EdgeSpec( | ||
| id="fetch-to-process", | ||
| source="fetch", | ||
| target="process", | ||
| condition=EdgeCondition.ON_SUCCESS, | ||
| priority=1, | ||
| ), | ||
| EdgeSpec( | ||
| id="process-to-generate", | ||
| source="process", | ||
| target="generate", | ||
| condition=EdgeCondition.ON_SUCCESS, | ||
| priority=1, | ||
| ), | ||
| EdgeSpec( | ||
| id="generate-to-approve", | ||
| source="generate", | ||
| target="approve", | ||
| condition=EdgeCondition.ON_SUCCESS, | ||
| priority=1, | ||
| ), | ||
| EdgeSpec( | ||
| id="approve-to-post", | ||
| source="approve", | ||
| target="post", | ||
| condition=EdgeCondition.ON_SUCCESS, | ||
| priority=1, | ||
| ), | ||
| ] | ||
|
|
||
| entry_node = "fetch" | ||
| entry_points = {"start": "fetch"} | ||
| terminal_nodes = ["post"] | ||
|
|
||
|
|
||
| class RSSTwitterAgent: | ||
| """Lightweight wrapper preserving the original interactive Playwright workflow.""" | ||
|
|
||
| def __init__(self): | ||
| self.goal = goal | ||
| self.nodes = nodes | ||
| self.edges = edges | ||
| self.entry_node = entry_node | ||
| self.entry_points = entry_points | ||
| self.terminal_nodes = terminal_nodes | ||
|
|
||
| async def start(self) -> None: | ||
| ok, msg = validate_ollama() | ||
| if not ok: | ||
| raise RuntimeError(msg) | ||
|
|
||
| async def stop(self) -> None: | ||
| return None | ||
|
|
||
| async def trigger_and_wait( | ||
| self, entry_point: str, input_data: dict, timeout: float | None = None | ||
| ) -> ExecutionResult: | ||
| feed_url = str(input_data.get("feed_url") or "https://news.ycombinator.com/rss") | ||
| max_articles = int(input_data.get("max_articles") or 3) | ||
| twitter_credential_ref = input_data.get("twitter_credential_ref") | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| workflow = await run_workflow( | ||
| feed_url=feed_url, | ||
| max_articles=max_articles, | ||
| twitter_credential_ref=( | ||
| str(twitter_credential_ref) if twitter_credential_ref else None | ||
| ), | ||
| ) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return ExecutionResult( | ||
| success=True, | ||
| output={ | ||
| "articles_json": workflow["articles_json"], | ||
| "processed_json": workflow["processed_json"], | ||
| "threads_json": workflow["threads_json"], | ||
| "approved_json": workflow["approved_json"], | ||
| "results_json": workflow["results_json"], | ||
| }, | ||
| steps_executed=5, | ||
| ) | ||
|
|
||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| async def run(self, context: dict) -> ExecutionResult: | ||
| await self.start() | ||
| try: | ||
| return await self.trigger_and_wait("start", context) | ||
| finally: | ||
| await self.stop() | ||
|
|
||
| def info(self) -> dict: | ||
| return { | ||
| "name": metadata.name, | ||
| "version": metadata.version, | ||
| "description": metadata.description, | ||
| "goal": {"name": self.goal.name, "description": self.goal.description}, | ||
| "nodes": [n.id for n in self.nodes], | ||
| "entry_node": self.entry_node, | ||
| "terminal_nodes": self.terminal_nodes, | ||
| } | ||
|
|
||
| def validate(self) -> dict: | ||
| errors: list[str] = [] | ||
| node_ids = {n.id for n in self.nodes} | ||
| if self.entry_node not in node_ids: | ||
| errors.append(f"Entry node '{self.entry_node}' not found") | ||
| for edge in self.edges: | ||
| if edge.source not in node_ids: | ||
| errors.append(f"Edge {edge.id}: source '{edge.source}' not found") | ||
| if edge.target not in node_ids: | ||
| errors.append(f"Edge {edge.id}: target '{edge.target}' not found") | ||
| return {"valid": not errors, "errors": errors, "warnings": []} | ||
|
|
||
|
|
||
| default_agent = RSSTwitterAgent() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| """Runtime configuration for RSS-to-Twitter Agent with Ollama.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| from dataclasses import dataclass, field | ||
|
|
||
| import httpx | ||
|
|
||
| OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434") | ||
| DEFAULT_MODEL = os.environ.get("LLM_MODEL", "ollama/llama3.2") | ||
|
|
||
|
|
||
| def _check_ollama_running() -> bool: | ||
| """Check if Ollama is running locally.""" | ||
| try: | ||
| with httpx.Client() as client: | ||
| resp = client.get(f"{OLLAMA_URL}/api/tags", timeout=2.0) | ||
| return resp.status_code == 200 | ||
| except Exception: | ||
| return False | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| def _get_model() -> str: | ||
| return DEFAULT_MODEL | ||
|
|
||
|
|
||
| @dataclass | ||
| class RuntimeConfig: | ||
| model: str = field(default_factory=_get_model) | ||
| temperature: float = 0.7 | ||
| max_tokens: int = 8000 | ||
| api_key: str | None = os.environ.get("LLM_API_KEY") | ||
| api_base: str | None = os.environ.get("LLM_API_BASE") | ||
|
|
||
|
|
||
| default_config = RuntimeConfig() | ||
|
|
||
|
|
||
| @dataclass | ||
| class AgentMetadata: | ||
| name: str = "RSS-to-Twitter Agent" | ||
| version: str = "1.1.0" | ||
| description: str = ( | ||
| "Automated content repurposing from RSS feeds to Twitter threads. " | ||
| "Uses Ollama for local LLM inference and Playwright for automated posting." | ||
| ) | ||
|
|
||
|
|
||
| metadata = AgentMetadata() | ||
|
|
||
|
|
||
| def validate_ollama() -> tuple[bool, str]: | ||
| if not _check_ollama_running(): | ||
| return ( | ||
| False, | ||
| "Ollama is not running. Start it with `ollama serve` and ensure your model is pulled.", | ||
| ) | ||
| return True, "" | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.