agent-workflow-ui 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. agent_workflow_ui-1.0.0/PKG-INFO +64 -0
  2. agent_workflow_ui-1.0.0/README.md +37 -0
  3. agent_workflow_ui-1.0.0/pyproject.toml +69 -0
  4. agent_workflow_ui-1.0.0/setup.cfg +4 -0
  5. agent_workflow_ui-1.0.0/src/agent_workflow_ui/SKILL.md +258 -0
  6. agent_workflow_ui-1.0.0/src/agent_workflow_ui/__init__.py +2 -0
  7. agent_workflow_ui-1.0.0/src/agent_workflow_ui/__main__.py +64 -0
  8. agent_workflow_ui-1.0.0/src/agent_workflow_ui/browser.py +70 -0
  9. agent_workflow_ui-1.0.0/src/agent_workflow_ui/config.py +74 -0
  10. agent_workflow_ui-1.0.0/src/agent_workflow_ui/http_endpoint.py +412 -0
  11. agent_workflow_ui-1.0.0/src/agent_workflow_ui/opencode_config.py +475 -0
  12. agent_workflow_ui-1.0.0/src/agent_workflow_ui/render/__init__.py +2 -0
  13. agent_workflow_ui-1.0.0/src/agent_workflow_ui/render/default_templates/ack.html.j2 +39 -0
  14. agent_workflow_ui-1.0.0/src/agent_workflow_ui/render/default_templates/increment-planning.html.j2 +254 -0
  15. agent_workflow_ui-1.0.0/src/agent_workflow_ui/render/default_templates/project-setup.html.j2 +851 -0
  16. agent_workflow_ui-1.0.0/src/agent_workflow_ui/render/engine.py +71 -0
  17. agent_workflow_ui-1.0.0/src/agent_workflow_ui/render/frontmatter.py +56 -0
  18. agent_workflow_ui-1.0.0/src/agent_workflow_ui/roles_processor.py +237 -0
  19. agent_workflow_ui-1.0.0/src/agent_workflow_ui/server.py +66 -0
  20. agent_workflow_ui-1.0.0/src/agent_workflow_ui/skill_installer.py +72 -0
  21. agent_workflow_ui-1.0.0/src/agent_workflow_ui/state.py +318 -0
  22. agent_workflow_ui-1.0.0/src/agent_workflow_ui/tools/__init__.py +1 -0
  23. agent_workflow_ui-1.0.0/src/agent_workflow_ui/tools/awf.py +1112 -0
  24. agent_workflow_ui-1.0.0/src/agent_workflow_ui/tools/forms.py +440 -0
  25. agent_workflow_ui-1.0.0/src/agent_workflow_ui/tools/templates.py +57 -0
  26. agent_workflow_ui-1.0.0/src/agent_workflow_ui.egg-info/PKG-INFO +64 -0
  27. agent_workflow_ui-1.0.0/src/agent_workflow_ui.egg-info/SOURCES.txt +29 -0
  28. agent_workflow_ui-1.0.0/src/agent_workflow_ui.egg-info/dependency_links.txt +1 -0
  29. agent_workflow_ui-1.0.0/src/agent_workflow_ui.egg-info/entry_points.txt +2 -0
  30. agent_workflow_ui-1.0.0/src/agent_workflow_ui.egg-info/requires.txt +8 -0
  31. agent_workflow_ui-1.0.0/src/agent_workflow_ui.egg-info/top_level.txt +1 -0
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-workflow-ui
3
+ Version: 1.0.0
4
+ Summary: MCP plugin that gives opencode agents tools for visual interaction (HTML forms, dashboards) with users
5
+ Author: EnerJize
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/EnerJizeIT/agentic-workflow
8
+ Project-URL: Repository, https://github.com/EnerJizeIT/agentic-workflow
9
+ Project-URL: Issues, https://github.com/EnerJizeIT/agentic-workflow/issues
10
+ Keywords: opencode,mcp,ai-agent,pipeline,dashboard,forms
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: mcp>=1.0
21
+ Requires-Dist: jinja2>=3.1
22
+ Requires-Dist: pyyaml>=6.0
23
+ Requires-Dist: awf>=1.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0; extra == "dev"
26
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
27
+
28
+ # agent-workflow-ui
29
+
30
+ MCP plugin для opencode. 29 typed tools (5 UI + 24 workflow).
31
+
32
+ ## Установка
33
+
34
+ ```bash
35
+ pip install -e ".[dev]"
36
+ ```
37
+
38
+ Plugin автоматически регистрируется в opencode через `awf init`.
39
+
40
+ ## Tools
41
+
42
+ ### UI (5)
43
+ `open_form`, `read_submit`, `cancel_form`, `list_pending_forms`, `list_templates`
44
+
45
+ ### Workflow (24)
46
+ `awf_init`, `awf_status`, `awf_start`, `awf_continue`, `awf_kill`, `awf_retry_stage`, `awf_baseline`, `awf_rollback`, `awf_approve`, `awf_reject`, `awf_report`, `awf_reset`, `awf_add_role`, `awf_analyze_roles`, `awf_dispatch_todo`, `awf_load_supervisor_context`, `awf_open_project_setup_form`, `awf_open_increment_planning_form`, `awf_open_pipeline_dashboard`, `awf_wait_for_event`, `awf_check_model_config`, `awf_current_step`, `awf_set_goal`, `awf_confirm_normalized`
47
+
48
+ Все workflow tools возвращают `next_action` — guide для supervisor.
49
+
50
+ ## Структура
51
+
52
+ ```
53
+ src/agent_workflow_ui/
54
+ ├── server.py # MCP server, 29 tools
55
+ ├── tools/awf.py # 24 awf wrappers (asyncio.to_thread + next_action)
56
+ ├── tools/forms.py # 5 UI wrappers
57
+ ├── http_endpoint.py # Form HTTP server
58
+ ├── opencode_config.py # Model discovery
59
+ ├── roles_processor.py # Role setup materialization
60
+ ├── state.py # Form state persistence
61
+ └── render/ # Jinja2 templates
62
+ ```
63
+
64
+ Все workflow tools — thin async wrappers над `awf.api.*()`. Бизнес-логика в `awf`.
@@ -0,0 +1,37 @@
1
+ # agent-workflow-ui
2
+
3
+ MCP plugin для opencode. 29 typed tools (5 UI + 24 workflow).
4
+
5
+ ## Установка
6
+
7
+ ```bash
8
+ pip install -e ".[dev]"
9
+ ```
10
+
11
+ Plugin автоматически регистрируется в opencode через `awf init`.
12
+
13
+ ## Tools
14
+
15
+ ### UI (5)
16
+ `open_form`, `read_submit`, `cancel_form`, `list_pending_forms`, `list_templates`
17
+
18
+ ### Workflow (24)
19
+ `awf_init`, `awf_status`, `awf_start`, `awf_continue`, `awf_kill`, `awf_retry_stage`, `awf_baseline`, `awf_rollback`, `awf_approve`, `awf_reject`, `awf_report`, `awf_reset`, `awf_add_role`, `awf_analyze_roles`, `awf_dispatch_todo`, `awf_load_supervisor_context`, `awf_open_project_setup_form`, `awf_open_increment_planning_form`, `awf_open_pipeline_dashboard`, `awf_wait_for_event`, `awf_check_model_config`, `awf_current_step`, `awf_set_goal`, `awf_confirm_normalized`
20
+
21
+ Все workflow tools возвращают `next_action` — guide для supervisor.
22
+
23
+ ## Структура
24
+
25
+ ```
26
+ src/agent_workflow_ui/
27
+ ├── server.py # MCP server, 29 tools
28
+ ├── tools/awf.py # 24 awf wrappers (asyncio.to_thread + next_action)
29
+ ├── tools/forms.py # 5 UI wrappers
30
+ ├── http_endpoint.py # Form HTTP server
31
+ ├── opencode_config.py # Model discovery
32
+ ├── roles_processor.py # Role setup materialization
33
+ ├── state.py # Form state persistence
34
+ └── render/ # Jinja2 templates
35
+ ```
36
+
37
+ Все workflow tools — thin async wrappers над `awf.api.*()`. Бизнес-логика в `awf`.
@@ -0,0 +1,69 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agent-workflow-ui"
7
+ version = "1.0.0"
8
+ description = "MCP plugin that gives opencode agents tools for visual interaction (HTML forms, dashboards) with users"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "EnerJize" }]
13
+ keywords = ["opencode", "mcp", "ai-agent", "pipeline", "dashboard", "forms"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ ]
23
+ dependencies = [
24
+ "mcp>=1.0",
25
+ "jinja2>=3.1",
26
+ "pyyaml>=6.0",
27
+ "awf>=1.0.0",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/EnerJizeIT/agentic-workflow"
32
+ Repository = "https://github.com/EnerJizeIT/agentic-workflow"
33
+ Issues = "https://github.com/EnerJizeIT/agentic-workflow/issues"
34
+
35
+ [project.optional-dependencies]
36
+ dev = [
37
+ "pytest>=7.0",
38
+ "pytest-cov>=4.0",
39
+ ]
40
+
41
+ [project.scripts]
42
+ agent-workflow-ui = "agent_workflow_ui.__main__:main"
43
+
44
+ [tool.setuptools.packages.find]
45
+ where = ["src"]
46
+ include = ["agent_workflow_ui*"]
47
+
48
+ [tool.setuptools.package-data]
49
+ agent_workflow_ui = ["render/default_templates/*.j2", "SKILL.md"]
50
+
51
+ [tool.ruff]
52
+ line-length = 100
53
+ target-version = "py310"
54
+
55
+ [tool.ruff.lint]
56
+ # F: pyflakes (real bugs)
57
+ # E/W: pycodestyle (PEP 8)
58
+ # I: isort (import sorting)
59
+ # UP: pyupgrade (modernize syntax)
60
+ select = ["F", "E", "W", "I", "UP"]
61
+ ignore = [
62
+ "E501", # line too long — handled by formatter, not linter
63
+ ]
64
+
65
+ [tool.ruff.lint.per-file-ignores]
66
+ "tests/**" = [
67
+ "F841", # unused local variables (test fixtures often have them)
68
+ "E501",
69
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,258 @@
1
+ # Agent Workflow UI
2
+
3
+ MCP plugin for [opencode](https://opencode.ai/) that gives agents tools for visual
4
+ interaction with users: HTML forms for structured input, dashboards for monitoring.
5
+
6
+ Use forms when chat is inefficient for the task at hand. Forms are a supplement
7
+ to chat, not a replacement.
8
+
9
+ ## When to use a form
10
+
11
+ **For project setup** (configuring pipeline + roles + models + supervisor):
12
+ use `awf_open_project_setup_form(project_dir)` — dedicated shortcut tool.
13
+ Plugin auto-populates roles/models/skills. Do NOT call `open_form`
14
+ directly for project setup, do NOT study template structure.
15
+
16
+ **For increment planning** (choosing decomposition strategy):
17
+ use `awf_open_increment_planning_form(variants, project_dir)` — user
18
+ picks from supervisor-generated variants.
19
+
20
+ **For pipeline monitoring** (watching agents work):
21
+ use `awf_open_pipeline_dashboard(project_dir)` — live dashboard with
22
+ auto-refresh 5s. Ask user before opening.
23
+
24
+ **For supervisor wake-up** (no more polling):
25
+ use `awf_wait_for_event(project_dir, timeout=120)` — ONE call blocks
26
+ until verify/blocked/checkpoint/done. Replaces sleep+status loops.
27
+
28
+ Call `open_form` directly when:
29
+
30
+ - **A file upload** is needed (ТЗ, product-vision, custom role `.md`).
31
+ - **Ad-hoc decision forks** with custom template you created.
32
+ - Monitoring a **long-running pipeline** (→ dashboard, see above).
33
+
34
+ ## When NOT to use a form (use chat instead)
35
+
36
+ - A simple **Y/N** answer.
37
+ - Choosing between **2-3 short options**.
38
+ - **Clarifying questions** during work.
39
+ - **Tone calibration** or approach discussion.
40
+ - Anything that can be answered in one line of text.
41
+
42
+ ## Usage pattern — NON-BLOCKING
43
+
44
+ **Always use `open_form` (non-blocking). Never use `open_form_and_wait` (it is not registered as MCP tool anyway).**
45
+
46
+ 1. Agent decides a form is needed.
47
+ 2. Call `open_form(template="...", data={...})` — opens form, returns `form_id` immediately.
48
+ 3. Tell the user in CLI: **"I opened a form in your browser. Fill it out, click Submit, then tell me here when you're done."**
49
+ 4. Agent is now **free** — can continue other work, answer questions, or wait for the user.
50
+ 5. When the user types something in CLI (e.g., "done", "submitted", "I filled it"):
51
+ - Call `read_submit(form_id)` to retrieve the submitted data.
52
+ - Analyze `data` and proceed.
53
+
54
+ **Why non-blocking?**
55
+
56
+ The agent stays responsive. The user controls the pace — they fill the form
57
+ when ready, then explicitly tell the agent to proceed. This avoids the agent
58
+ "hanging" silently while the user is still thinking.
59
+
60
+ **Critical:** You MUST tell the user to come back to CLI and notify you after
61
+ submitting. The form's "Submitted!" page also reminds them, but you should
62
+ say it in your message too.
63
+
64
+ ### What to tell the user
65
+
66
+ After calling `open_form`, ALWAYS say something like:
67
+
68
+ > "I've opened a form in your browser (Form ID: FORM-...).
69
+ > Please fill it out and click Submit.
70
+ > When done, come back here and tell me — I'll pick up your answers."
71
+
72
+ ### When the user says "done" / "submitted" / "filled it"
73
+
74
+ Call `read_submit(form_id)` immediately to get the data. Don't make the user
75
+ repeat themselves. If `read_submit` returns `submitted: false`, tell the user
76
+ the form hasn't been submitted yet and ask them to check.
77
+
78
+ ## Tool reference
79
+
80
+ ### `open_form(template, data, project_dir?, ttl_seconds?)` — PRIMARY TOOL
81
+
82
+ Render and open an HTML form in the user's browser. Non-blocking — returns immediately.
83
+
84
+ **Arguments:**
85
+ - `template` (str, required) — Template name without extension (e.g., "project-setup").
86
+ - `data` (dict, optional) — Variables to render in the template.
87
+ - `project_dir` (str, optional but **required for project-setup**) — Absolute path to awf project root. When provided, submit triggers automatic materialization via `awf.api.apply_project_setup` (writes pipeline.yaml, patches config.yaml + supervisor.md). Without it, submit is saved to `inputs/` only — agent must materialize manually. MCP subprocess runs in $HOME, so cwd-based detection does NOT work — pass explicitly.
88
+ - `ttl_seconds` (int, optional) — Auto-expire form after N seconds.
89
+
90
+ **Returns:** `{form_id, browser_opened, submit_url, expires_at?, error?}`
91
+
92
+ **MUST for project-setup:** Always pass `project_dir` explicitly:
93
+ ```python
94
+ open_form(template="project-setup", data={...}, project_dir="/abs/path/to/project")
95
+ ```
96
+
97
+ ### `read_submit(form_id)` — USE WHEN USER SAYS "DONE"
98
+
99
+ Check if user submitted the form. Non-blocking — returns immediately.
100
+
101
+ **Returns:** `{submitted, form_id, status, data?, submitted_at?, error?}`
102
+
103
+ **Status values:** `pending`, `submitted`, `cancelled`, `expired`, `unknown`, `error`
104
+
105
+ ### `cancel_form(form_id)`
106
+
107
+ Cancel a pending form. Future HTTP submits for this form_id are ignored.
108
+
109
+ **Returns:** `{cancelled, form_id, reason?}`
110
+
111
+ ### `list_pending_forms()`
112
+
113
+ List forms opened but not yet submitted, cancelled, or expired.
114
+
115
+ **Returns:** `{pending: [{form_id, template, opened_at, age_seconds}], count}`
116
+
117
+ ### `list_templates()`
118
+
119
+ List available form templates with metadata.
120
+
121
+ **Returns:** `{templates: [{name, source, description, required_data_keys, optional_data_keys}]}`
122
+
123
+ ## Available templates
124
+
125
+ Call `list_templates` to see what's available at runtime.
126
+
127
+ ### Primary (MVP)
128
+
129
+ | Template | Description | Required data keys |
130
+ |---|---|---|
131
+ | `project-setup` | Composite form: context + ТЗ files + supervisor + team (with models). Models pulled from opencode automatically. | `available_roles` |
132
+
133
+ Plugin injects automatically into every template:
134
+ - `form_id`, `submit_url`, `template_name`
135
+ - `available_models`, `recent_models` (from opencode CLI + SQLite history)
136
+ - `custom_supervisor_roles`, `custom_agents` (from `~/.config/awf/roles/`)
137
+ - `existing_supervisor_slugs`, `existing_agent_slugs` (for client-side conflict detection)
138
+
139
+ ### Future templates
140
+
141
+ Templates for scenarios 2-6 (decision forks, blockage recovery, dashboards, priority planning, onboarding wizard) will be added as those scenarios are implemented. Currently only `project-setup` ships as a default template.
142
+
143
+ **Until future scenarios are implemented:** Use `project-setup` for project configuration. Use chat for everything else.
144
+
145
+ ## Creating new templates (agent-driven)
146
+
147
+ **Only the agent (you, LLM) can create new templates.** The user does not edit `.agentic/templates/` manually — if a custom form is needed, you create it.
148
+
149
+ ### How to create a new template
150
+
151
+ 1. **Front (HTML form):** write a Jinja2 template at `.agentic/templates/<name>.html.j2`. Structure:
152
+ ```jinja2
153
+ ---
154
+ description: What this form does
155
+ required_data_keys:
156
+ - some_required_var
157
+ optional_data_keys:
158
+ - some_optional_var
159
+ ---
160
+ <!DOCTYPE html>
161
+ <html>
162
+ <head><title>...</title></head>
163
+ <body>
164
+ <form action="{{ submit_url }}" method="POST">
165
+ <input type="hidden" name="form_id" value="{{ form_id }}">
166
+ <!-- form fields here -->
167
+ <button type="submit">Submit</button>
168
+ </form>
169
+ </body>
170
+ </html>
171
+ ```
172
+
173
+ 2. **Back (interpretation):** describe in supervisor.md (or in your response to the user) how to interpret the submit data. Plugin's HTTP endpoint saves the raw form data to `inputs/<form_id>.yaml` — you read it via `read_submit` and parse the fields yourself.
174
+
175
+ 3. **Use:** call `open_form(template="<name>", data={...})`. Plugin's `ChoiceLoader` finds project templates first, then defaults — so your new template is available immediately.
176
+
177
+ ### Template variables available
178
+
179
+ Every template gets these injected by plugin:
180
+ - `{{ form_id }}` — current form ID
181
+ - `{{ submit_url }}` — POST endpoint
182
+ - `{{ template_name }}` — current template name
183
+ - `{{ available_models }}`, `{{ recent_models }}` — opencode models
184
+ - `{{ custom_supervisor_roles }}`, `{{ custom_agents }}` — saved roles
185
+ - Plus any keys you pass in `data` argument
186
+
187
+ ### Important rules
188
+
189
+ - Always use `<form action="{{ submit_url }}" method="POST">` — that's how submits reach the plugin.
190
+ - Always include `<input type="hidden" name="form_id" value="{{ form_id }}">`.
191
+ - Plugin uses Jinja2 with autoescape — user input is safe.
192
+ - YAML frontmatter is stripped from output automatically.
193
+
194
+ ## Mistakes to avoid
195
+
196
+ - **DO NOT** use blocking wait tools — they are not registered. Use `open_form` + user notification pattern.
197
+ - **DO NOT** forget to tell the user to come back to CLI after submitting.
198
+ - **DO NOT** open a form for Y/N questions — use chat.
199
+ - **DO NOT** open more than 3 forms simultaneously — the user will be confused.
200
+ - **DO NOT** use forms as a chat replacement — they're a supplement.
201
+ - **DO NOT** invent template names — only `project-setup` ships as default. Future scenarios will add more.
202
+
203
+ ## Example workflow
204
+
205
+ ```
206
+ Agent: "I need to configure your project. Let me open a form."
207
+
208
+ → open_form(template="project-setup", project_dir="/abs/path/to/project", data={"available_roles": [
209
+ {"id": "backend-dev", "title": "Backend Developer"},
210
+ {"id": "frontend-dev", "title": "Frontend Developer"},
211
+ {"id": "tester", "title": "Tester"}
212
+ ]})
213
+ Returns: {form_id: "FORM-20260726143022-a1b2", browser_opened: true, submit_url: "..."}
214
+
215
+ Agent (to user): "I've opened project setup form in your browser.
216
+ Fill context, pick supervisor, build your team, click Submit.
217
+ When done, tell me here."
218
+
219
+ [Agent is free — can answer other questions, do other work, or wait]
220
+
221
+ User (in CLI): "Done!"
222
+
223
+ → read_submit("FORM-20260726143022-a1b2")
224
+ Returns: {submitted: true, data: {
225
+ context_message: "...",
226
+ team_config: '[{"type":"default","agent":"backend-dev","model":"anthropic/claude-3.5"}]',
227
+ spec_files_json: '[...]',
228
+ ...
229
+ }}
230
+
231
+ After submit, plugin writes:
232
+ - Custom roles to global + project (BD-3-B, BD-5).
233
+ - Pipeline stages to .agentic/pipelines/default.yaml (BD-9).
234
+ - Per-role models to .agentic/config.yaml (BD-32).
235
+
236
+ Next `awf start` runs the pipeline. Supervisor (interactive session or
237
+ subprocess in --auto mode) creates TODO from phases/plan.md, agents
238
+ execute, supervisor verifies.
239
+
240
+ Optional: run `awf analyze-roles` (BD-31) before `awf start` if roles
241
+ have overlapping zones — adds disambiguation patches to role files.
242
+
243
+ Agent (to user): "Got it. Generating .agentic/config.yaml and roles/..."
244
+ [Agent writes project files using its own tools — plugin does NOT generate them]
245
+ ```
246
+
247
+ ## Troubleshooting
248
+
249
+ - **Browser didn't open:** The `open_form` result may have `browser_opened: false`
250
+ with an error message. Tell the user the form HTML is at a local file path, or
251
+ offer to collect the answer via chat instead.
252
+ - **User can't access browser (SSH/remote):** Offer a CLI fallback — ask the user
253
+ to type their choice in the chat. Call `cancel_form` to clean up.
254
+ - **User says "done" but read_submit returns `submitted: false`:** The user may
255
+ have closed the form without submitting. Ask them to check and submit again.
256
+ - **Form expired:** If `read_submit` returns `status: "expired"`, open a new form
257
+ with a longer TTL or ask via chat.
258
+ - **Conflict on save:** If the user is saving a custom role with a name that already exists, the form will show a JS confirm dialog. If they confirm — overwrite; if cancel — they change the name or uncheck save.
@@ -0,0 +1,2 @@
1
+ """agent-workflow-ui — MCP plugin for visual interaction in opencode workflows."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,64 @@
1
+ """Entry point: python -m agent_workflow_ui"""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from .config import detect_project_dir, ensure_directories, load
9
+ from .http_endpoint import start_http_server
10
+ from .opencode_config import read_opencode_models, read_recent_models
11
+ from .render.engine import create_env
12
+ from .server import create_server
13
+ from .skill_installer import ensure_skill_installed
14
+ from .state import get_registry, set_config, set_http_port, set_jinja_env, set_project_dir
15
+
16
+ log = logging.getLogger(__name__)
17
+
18
+ DEFAULT_TEMPLATES_DIR = Path(__file__).parent / "render" / "default_templates"
19
+
20
+
21
+ def main() -> int:
22
+ """Run the MCP server on stdio transport, with HTTP endpoint in background."""
23
+ logging.basicConfig(
24
+ level=logging.INFO,
25
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
26
+ stream=sys.stderr,
27
+ )
28
+
29
+ try:
30
+ # Lazy skill install: copy SKILL.md to ~/.config/opencode/skills/agent-workflow-ui/
31
+ # Idempotent — overwrites if bundled version differs. No-op if already current.
32
+ ensure_skill_installed()
33
+
34
+ config = load()
35
+ ensure_directories(config)
36
+ set_config(config)
37
+ set_project_dir(detect_project_dir())
38
+
39
+ env = create_env([config.templates_dir, DEFAULT_TEMPLATES_DIR])
40
+ env.globals["available_models"] = read_opencode_models()
41
+ env.globals["recent_models"] = read_recent_models()
42
+ set_jinja_env(env)
43
+
44
+ registry = get_registry()
45
+ server, port = start_http_server(config, registry)
46
+ set_http_port(port)
47
+ log.info("agent_workflow_ui started (HTTP on port %d)", port)
48
+
49
+ mcp = create_server()
50
+ mcp.run() # blocks; stdio transport
51
+
52
+ server.shutdown()
53
+ return 0
54
+ except KeyboardInterrupt:
55
+ log.info("agent_workflow_ui interrupted by user")
56
+ return 130
57
+ except Exception:
58
+ log.exception("agent_workflow_ui crashed during startup/runtime")
59
+ print("FATAL: agent_workflow_ui failed to start", file=sys.stderr)
60
+ return 1
61
+
62
+
63
+ if __name__ == "__main__":
64
+ sys.exit(main())
@@ -0,0 +1,70 @@
1
+ """Cross-platform browser open via stdlib subprocess."""
2
+ from __future__ import annotations
3
+
4
+ import platform
5
+ import shutil
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+
10
+ def detect_open_command(configured: str = "auto") -> str:
11
+ """Detect the platform-appropriate command to open a file in the browser.
12
+
13
+ Args:
14
+ configured: "auto" for platform detection, or an explicit command name.
15
+
16
+ Returns:
17
+ Command string: "xdg-open" (Linux), "open" (macOS), "explorer" (Windows).
18
+ """
19
+ if configured != "auto":
20
+ return configured
21
+
22
+ system = platform.system()
23
+ if system == "Linux":
24
+ return "xdg-open"
25
+ if system == "Darwin":
26
+ return "open"
27
+ if system == "Windows":
28
+ return "explorer"
29
+ # Fallback
30
+ return "xdg-open"
31
+
32
+
33
+ def open_path(target: Path | str, command: str = "auto") -> tuple[bool, str]:
34
+ """Open a file path or URL in the default browser.
35
+
36
+ Fire-and-forget: does not block the calling process beyond a short timeout.
37
+ stdout/stderr are captured so they don't interfere with MCP stdio transport.
38
+
39
+ Args:
40
+ target: File path or URL to open.
41
+ command: "auto" (detect by platform), or explicit command name.
42
+
43
+ Returns:
44
+ Tuple (success, message). On success: (True, "opened via <cmd>").
45
+ On failure: (False, error description).
46
+ """
47
+ cmd = detect_open_command(command)
48
+
49
+ if shutil.which(cmd) is None:
50
+ return False, f"Browser open command '{cmd}' not found on PATH"
51
+
52
+ try:
53
+ result = subprocess.run(
54
+ [cmd, str(target)],
55
+ capture_output=True,
56
+ timeout=5,
57
+ check=False,
58
+ )
59
+ if result.returncode == 0:
60
+ return True, f"opened via {cmd}"
61
+
62
+ stderr = result.stderr.decode("utf-8", errors="replace").strip()
63
+ return (
64
+ False,
65
+ f"'{cmd}' exited with code {result.returncode}: {stderr or 'unknown error'}",
66
+ )
67
+ except subprocess.TimeoutExpired:
68
+ return False, f"'{cmd}' timed out after 5s"
69
+ except FileNotFoundError:
70
+ return False, f"Browser open command '{cmd}' not found"
@@ -0,0 +1,74 @@
1
+ """Configuration via environment variables."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import tempfile
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+
10
+ @dataclass
11
+ class Config:
12
+ """Resolved plugin configuration."""
13
+ inputs_dir: Path
14
+ templates_dir: Path
15
+ dashboards_dir: Path
16
+ http_port: int
17
+ open_browser_cmd: str
18
+ temp_dir: Path
19
+ default_ttl_seconds: int
20
+
21
+
22
+ def load() -> Config:
23
+ """Load config from environment variables.
24
+
25
+ Paths are resolved against the current working directory (which is the
26
+ opencode project root when the plugin runs).
27
+ """
28
+ cwd = Path.cwd()
29
+
30
+ def _path(env_var: str, default: str) -> Path:
31
+ value = os.environ.get(env_var, default)
32
+ p = Path(value)
33
+ if not p.is_absolute():
34
+ p = (cwd / p).resolve()
35
+ return p
36
+
37
+ return Config(
38
+ inputs_dir=_path("AWF_INPUTS_DIR", ".agentic/inputs"),
39
+ templates_dir=_path("AWF_TEMPLATES_DIR", ".agentic/templates"),
40
+ dashboards_dir=_path("AWF_DASHBOARDS_DIR", ".agentic/dashboards"),
41
+ http_port=int(os.environ.get("AWF_HTTP_PORT", "0")),
42
+ open_browser_cmd=os.environ.get("AWF_OPEN_BROWSER_CMD", "auto"),
43
+ temp_dir=_path("AWF_TEMP_DIR", tempfile.gettempdir()),
44
+ default_ttl_seconds=int(os.environ.get("AWF_DEFAULT_TTL_SECONDS", "86400")),
45
+ )
46
+
47
+
48
+ def detect_project_dir() -> Path | None:
49
+ """Detect active awf project directory.
50
+
51
+ Returns Path.cwd() if it contains .agentic/ directory, None otherwise.
52
+
53
+ .. note::
54
+ When opencode supports setting env vars for MCP subprocess, switch to
55
+ ``AWF_PROJECT_DIR`` env var as primary source. Until then,
56
+ agent-passed ``project_dir`` in ``open_form(data)`` is canonical.
57
+ """
58
+ import logging
59
+
60
+ log = logging.getLogger(__name__)
61
+ cwd = Path.cwd()
62
+ agentic = cwd / ".agentic"
63
+ if agentic.is_dir():
64
+ log.debug("Detected awf project directory: %s", cwd)
65
+ return cwd
66
+ log.debug("No .agentic/ found at %s — not inside an awf project", cwd)
67
+ return None
68
+
69
+
70
+ def ensure_directories(config: Config) -> None:
71
+ """Create runtime directories if missing. Idempotent."""
72
+ config.inputs_dir.mkdir(parents=True, exist_ok=True)
73
+ config.templates_dir.mkdir(parents=True, exist_ok=True)
74
+ config.dashboards_dir.mkdir(parents=True, exist_ok=True)