-
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
1e3eb7d
15680c3
19ae3e5
4931ff2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # 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 | ||
| 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. | ||
| 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"] |
| 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() |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,195 @@ | ||||||||||||||||||||||
| """Agent metadata + simple execution wrapper for RSS-to-Twitter Playwright flow.""" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import asyncio | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 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") | ||||||||||||||||||||||
| raw_max_articles = input_data.get("max_articles") | ||||||||||||||||||||||
| max_articles = 3 if raw_max_articles in (None, "") else int(raw_max_articles) | ||||||||||||||||||||||
|
||||||||||||||||||||||
| raw_max_articles = input_data.get("max_articles") | |
| max_articles = 3 if raw_max_articles in (None, "") else int(raw_max_articles) | |
| raw_max_articles = input_data.get("max_articles") | |
| if raw_max_articles in (None, ""): | |
| max_articles = 3 | |
| else: | |
| try: | |
| max_articles = int(raw_max_articles) | |
| except (ValueError, TypeError): | |
| max_articles = 3 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/templates/rss_twitter_agent/agent.py` around lines 128 - 129, Guard
parsing of raw_max_articles to avoid ValueError: when reading
input_data.get("max_articles") (raw_max_articles) wrap the conversion to int
used for max_articles in a safe check/try-except (or use str.isdigit() plus
fallback) so non-numeric strings (e.g., "abc" or whitespace) do not raise and
instead set max_articles to the default 3; include an optional warning/log via
the existing logger if available and keep the existing behavior for None/""
values.
Uh oh!
There was an error while loading. Please reload this page.