optio-demo 0.1.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.
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: optio-demo
3
+ Version: 0.1.0
4
+ Summary: Demo application exercising all optio-core features
5
+ Author-email: Kristof Csillag <kristof.csillag@deai-labs.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/deai-network/optio
8
+ Project-URL: Repository, https://github.com/deai-network/optio
9
+ Project-URL: Issues, https://github.com/deai-network/optio/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: optio-core[redis]<0.3,>=0.2
22
+ Requires-Dist: marimo>=0.9
23
+ Requires-Dist: optio-opencode<0.2,>=0.1
24
+ Requires-Dist: optio-claudecode<0.2,>=0.1
25
+ Requires-Dist: watchfiles>=1.0
26
+
27
+ # optio-demo
28
+
29
+ A demo application that exercises all [optio-core](../optio-core) features through a collection of whimsical long-running task trees. Run it, open the dashboard, and watch processes unfold in real time — or cancel, relaunch, and dismiss them from the UI.
30
+
31
+ ## Prerequisites
32
+
33
+ - **Docker** — for MongoDB and Redis (started automatically by `make install`)
34
+ - **Python 3.11+** — for the demo worker
35
+ - **Node.js** — for the dashboard (`npx optio-dashboard`)
36
+
37
+ ## Quick start
38
+
39
+ ```bash
40
+ # 1. Start infrastructure and install Python dependencies
41
+ make install
42
+
43
+ # 2. Start the demo worker
44
+ make run
45
+
46
+ # 3. In a second terminal, launch the dashboard
47
+ make run-dashboard
48
+ ```
49
+
50
+ Then open [http://localhost:3000](http://localhost:3000).
51
+
52
+ From the dashboard you can **launch**, **cancel**, and **dismiss** any process. Click a process name to drill into its task tree with live progress and a log panel.
53
+
54
+ ## Task themes
55
+
56
+ ### Terraforming Mars (~30 min)
57
+
58
+ The flagship showcase. A four-level nested task tree covering all progress helpers (`sequential_progress`, `average_progress`, `mapped_progress`), parallel groups with concurrency limits, `survive_failure` subtasks, and a full cooperative cancellation cascade. Metadata is set at the root and inherited by children.
59
+
60
+ ### Organizing Your Home (~10 min)
61
+
62
+ A household chore tree mixing sequential and parallel subtasks. Demonstrates `survive_failure` (some chores are optional), non-cancellable tasks (you can't un-wash the dishes mid-stream), and indeterminate progress bars for tasks that can't report a percentage.
63
+
64
+ ### The Great Museum Heist (~8 min)
65
+
66
+ A parallel heist plan where things go wrong. Exercises parallel failure propagation, cascading errors across sibling tasks, and the `warning` field — the dashboard shows a confirmation popover before you relaunch a task that carries a warning.
67
+
68
+ ### Intergalactic Music Festival
69
+
70
+ Eight generated concert tasks, one per venue (Europa, Titan, Ganymede, …). Each is created from the same template but with different `params` and `metadata` (genre, audience size, song count, encore flag). Demonstrates generating task lists programmatically and conditional child execution driven by params.
71
+
72
+ ### Your 15-min Wake-up Call
73
+
74
+ A cron-scheduled task that fires every 15 minutes. Demonstrates `cron` scheduling and automatic re-launch — after each run completes it returns to `scheduled` state and fires again at the next interval.
75
+
76
+ ## Feature coverage
77
+
78
+ | Feature | Terraforming Mars | Organizing Home | Museum Heist | Music Festival | Wake-up Call |
79
+ |---------|:-----------------:|:---------------:|:------------:|:--------------:|:------------:|
80
+ | Sequential progress | x | | | x | |
81
+ | Average progress | x | | | | |
82
+ | Mapped progress | x | | | | |
83
+ | Parallel groups | x | x | x | | |
84
+ | max_concurrency | x | | | | |
85
+ | survive_failure | x | x | | | |
86
+ | Non-cancellable tasks | | x | | | |
87
+ | Indeterminate progress | | x | | | |
88
+ | Parallel failure / cascading errors | | | x | | |
89
+ | warning field | | | x | | |
90
+ | params / metadata | | | | x | |
91
+ | Metadata inheritance | x | | | x | |
92
+ | Cron scheduling | | | | | x |
93
+ | Cooperative cancellation | x | | | | |
94
+ | Deep nesting (4 levels) | x | | | | |
95
+ | Generated tasks (loop) | | | | x | |
96
+
97
+ ## Widget smoke test (Task 21)
98
+
99
+ Exercises all four widget primitives end-to-end via a live marimo notebook.
100
+
101
+ 1. `docker compose up` in this directory to start MongoDB + Redis.
102
+ 2. `pnpm --filter optio-dashboard dev` in another terminal to serve the dashboard.
103
+ 3. `python -m optio_demo` in a third terminal to run the demo worker (make sure `pip install -e .` or equivalent has been run so the `optio-demo` package is available).
104
+ 4. Open the dashboard in a browser. Authenticate if prompted.
105
+ 5. Find the "Marimo Notebook" task in the process list. Click launch.
106
+ 6. Click the running process — the iframe widget should mount and show a live marimo notebook.
107
+ 7. Interact with the notebook. Reactive updates flow through the widget proxy.
108
+ 8. Cancel the process. The "session ended" banner overlays the iframe; the marimo subprocess is terminated.
109
+ 9. Dismiss. The iframe unmounts; `widgetUpstream` and `widgetData` are cleared on the process document.
@@ -0,0 +1,83 @@
1
+ # optio-demo
2
+
3
+ A demo application that exercises all [optio-core](../optio-core) features through a collection of whimsical long-running task trees. Run it, open the dashboard, and watch processes unfold in real time — or cancel, relaunch, and dismiss them from the UI.
4
+
5
+ ## Prerequisites
6
+
7
+ - **Docker** — for MongoDB and Redis (started automatically by `make install`)
8
+ - **Python 3.11+** — for the demo worker
9
+ - **Node.js** — for the dashboard (`npx optio-dashboard`)
10
+
11
+ ## Quick start
12
+
13
+ ```bash
14
+ # 1. Start infrastructure and install Python dependencies
15
+ make install
16
+
17
+ # 2. Start the demo worker
18
+ make run
19
+
20
+ # 3. In a second terminal, launch the dashboard
21
+ make run-dashboard
22
+ ```
23
+
24
+ Then open [http://localhost:3000](http://localhost:3000).
25
+
26
+ From the dashboard you can **launch**, **cancel**, and **dismiss** any process. Click a process name to drill into its task tree with live progress and a log panel.
27
+
28
+ ## Task themes
29
+
30
+ ### Terraforming Mars (~30 min)
31
+
32
+ The flagship showcase. A four-level nested task tree covering all progress helpers (`sequential_progress`, `average_progress`, `mapped_progress`), parallel groups with concurrency limits, `survive_failure` subtasks, and a full cooperative cancellation cascade. Metadata is set at the root and inherited by children.
33
+
34
+ ### Organizing Your Home (~10 min)
35
+
36
+ A household chore tree mixing sequential and parallel subtasks. Demonstrates `survive_failure` (some chores are optional), non-cancellable tasks (you can't un-wash the dishes mid-stream), and indeterminate progress bars for tasks that can't report a percentage.
37
+
38
+ ### The Great Museum Heist (~8 min)
39
+
40
+ A parallel heist plan where things go wrong. Exercises parallel failure propagation, cascading errors across sibling tasks, and the `warning` field — the dashboard shows a confirmation popover before you relaunch a task that carries a warning.
41
+
42
+ ### Intergalactic Music Festival
43
+
44
+ Eight generated concert tasks, one per venue (Europa, Titan, Ganymede, …). Each is created from the same template but with different `params` and `metadata` (genre, audience size, song count, encore flag). Demonstrates generating task lists programmatically and conditional child execution driven by params.
45
+
46
+ ### Your 15-min Wake-up Call
47
+
48
+ A cron-scheduled task that fires every 15 minutes. Demonstrates `cron` scheduling and automatic re-launch — after each run completes it returns to `scheduled` state and fires again at the next interval.
49
+
50
+ ## Feature coverage
51
+
52
+ | Feature | Terraforming Mars | Organizing Home | Museum Heist | Music Festival | Wake-up Call |
53
+ |---------|:-----------------:|:---------------:|:------------:|:--------------:|:------------:|
54
+ | Sequential progress | x | | | x | |
55
+ | Average progress | x | | | | |
56
+ | Mapped progress | x | | | | |
57
+ | Parallel groups | x | x | x | | |
58
+ | max_concurrency | x | | | | |
59
+ | survive_failure | x | x | | | |
60
+ | Non-cancellable tasks | | x | | | |
61
+ | Indeterminate progress | | x | | | |
62
+ | Parallel failure / cascading errors | | | x | | |
63
+ | warning field | | | x | | |
64
+ | params / metadata | | | | x | |
65
+ | Metadata inheritance | x | | | x | |
66
+ | Cron scheduling | | | | | x |
67
+ | Cooperative cancellation | x | | | | |
68
+ | Deep nesting (4 levels) | x | | | | |
69
+ | Generated tasks (loop) | | | | x | |
70
+
71
+ ## Widget smoke test (Task 21)
72
+
73
+ Exercises all four widget primitives end-to-end via a live marimo notebook.
74
+
75
+ 1. `docker compose up` in this directory to start MongoDB + Redis.
76
+ 2. `pnpm --filter optio-dashboard dev` in another terminal to serve the dashboard.
77
+ 3. `python -m optio_demo` in a third terminal to run the demo worker (make sure `pip install -e .` or equivalent has been run so the `optio-demo` package is available).
78
+ 4. Open the dashboard in a browser. Authenticate if prompted.
79
+ 5. Find the "Marimo Notebook" task in the process list. Click launch.
80
+ 6. Click the running process — the iframe widget should mount and show a live marimo notebook.
81
+ 7. Interact with the notebook. Reactive updates flow through the widget proxy.
82
+ 8. Cancel the process. The "session ended" banner overlays the iframe; the marimo subprocess is terminated.
83
+ 9. Dismiss. The iframe unmounts; `widgetUpstream` and `widgetData` are cleared on the process document.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "optio-demo"
7
+ version = "0.1.0"
8
+ description = "Demo application exercising all optio-core features"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ { name = "Kristof Csillag", email = "kristof.csillag@deai-labs.com" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Operating System :: MacOS",
23
+ "Topic :: Software Development",
24
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
25
+ ]
26
+ dependencies = [
27
+ "optio-core[redis]>=0.2,<0.3",
28
+ "marimo>=0.9",
29
+ "optio-opencode>=0.1,<0.2",
30
+ "optio-claudecode>=0.1,<0.2",
31
+ "watchfiles>=1.0",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/deai-network/optio"
36
+ Repository = "https://github.com/deai-network/optio"
37
+ Issues = "https://github.com/deai-network/optio/issues"
38
+
39
+ [tool.setuptools.packages.find]
40
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,37 @@
1
+ """Optio demo application — exercises all optio-core features."""
2
+
3
+ import asyncio
4
+ import os
5
+ import logging
6
+
7
+ from motor.motor_asyncio import AsyncIOMotorClient
8
+ from optio_core.lifecycle import Optio
9
+
10
+ from optio_demo.tasks import get_task_definitions
11
+
12
+ logging.basicConfig(level=logging.INFO)
13
+
14
+
15
+ async def main():
16
+ mongo_url = os.environ.get("MONGODB_URL", "mongodb://localhost:27017/optio-demo")
17
+ redis_url = os.environ.get("REDIS_URL", "redis://localhost:6379")
18
+ prefix = os.environ.get("OPTIO_PREFIX", "optio")
19
+
20
+ db_name = mongo_url.rsplit("/", 1)[-1]
21
+ client = AsyncIOMotorClient(mongo_url)
22
+ db = client[db_name]
23
+
24
+ fw = Optio()
25
+ await fw.init(
26
+ mongo_db=db,
27
+ prefix=prefix,
28
+ redis_url=redis_url,
29
+ services={"optio": fw, "db": db, "prefix": prefix},
30
+ get_task_definitions=get_task_definitions,
31
+ )
32
+
33
+ await fw.run()
34
+
35
+
36
+ if __name__ == "__main__":
37
+ asyncio.run(main())
@@ -0,0 +1,20 @@
1
+ import marimo
2
+
3
+ __generated_with = "0.9.0"
4
+ app = marimo.App()
5
+
6
+
7
+ @app.cell
8
+ def __():
9
+ import marimo as mo
10
+ return (mo,)
11
+
12
+
13
+ @app.cell
14
+ def __(mo):
15
+ mo.md("# Optio widget demo\n\nHello from marimo — this notebook is served through the optio widget proxy.")
16
+ return
17
+
18
+
19
+ if __name__ == "__main__":
20
+ app.run()
@@ -0,0 +1,30 @@
1
+ """Task definitions for the optio demo application."""
2
+
3
+ from optio_core.models import TaskInstance, ProcessMetadataFilter
4
+
5
+ from optio_demo.tasks.terraforming import get_tasks as terraforming_tasks
6
+ from optio_demo.tasks.home import get_tasks as home_tasks
7
+ from optio_demo.tasks.heist import get_tasks as heist_tasks
8
+ from optio_demo.tasks.festival import get_tasks as festival_tasks
9
+ from optio_demo.tasks.wakeup import get_tasks as wakeup_tasks
10
+ from optio_demo.tasks.marimo import get_tasks as marimo_tasks
11
+ from optio_demo.tasks.opencode import get_tasks as opencode_tasks
12
+ from optio_demo.tasks.client_directed import get_tasks as client_directed_tasks
13
+ from optio_demo.tasks.claudecode import get_tasks as claudecode_tasks
14
+
15
+
16
+ async def get_task_definitions(
17
+ services: dict,
18
+ metadata_filter: ProcessMetadataFilter | None = None,
19
+ ) -> list[TaskInstance]:
20
+ return [
21
+ *terraforming_tasks(),
22
+ *home_tasks(),
23
+ *heist_tasks(),
24
+ *festival_tasks(),
25
+ *wakeup_tasks(),
26
+ *marimo_tasks(),
27
+ *opencode_tasks(),
28
+ *client_directed_tasks(),
29
+ *await claudecode_tasks(services),
30
+ ]
@@ -0,0 +1,215 @@
1
+ """Reference demo tasks for optio-claudecode — the seed lifecycle.
2
+
3
+ Exposes a static **"Setup Claude Code seed"** task plus one dynamic
4
+ **"Claude Code demo — {name}"** task per captured seed. The operator
5
+ launches setup, logs into Claude Code interactively (``/login``) in the
6
+ ttyd TUI, configures plugins, then stops the task; on teardown the
7
+ environment is captured as a seed and a seed-pinned demo task appears
8
+ (via in-process ``resync``). Authentication comes from the seed, not an
9
+ ``ANTHROPIC_API_KEY`` — claude runs under HOME-isolation
10
+ (``HOME=<workdir>/home``), so the host user's ``~/.claude`` is not
11
+ inherited; the seed supplies credentials/settings/plugins instead.
12
+
13
+ Defaults to local mode; set the ``OPTIO_CLAUDECODE_DEMO_SSH_HOST``
14
+ environment variable to run via SSH on a remote host. Relevant env vars
15
+ (all optional except ``_HOST``):
16
+
17
+ - ``OPTIO_CLAUDECODE_DEMO_SSH_HOST`` — enables remote mode.
18
+ - ``OPTIO_CLAUDECODE_DEMO_SSH_USER`` — default: ``$USER`` on the worker.
19
+ - ``OPTIO_CLAUDECODE_DEMO_SSH_KEY_PATH`` — default: ``~/.ssh/id_ed25519``.
20
+ - ``OPTIO_CLAUDECODE_DEMO_SSH_PORT`` — default: ``22``.
21
+
22
+ Hook walkthrough (mirrors the opencode demo), wired on each seed task:
23
+
24
+ - ``before_execute`` runs ``whoami`` on the host (proves the hook fires
25
+ inside the session pipeline) and ships ``context.txt`` into the
26
+ workdir via ``copy_file`` (bytes source). The LLM is instructed to
27
+ read that file, so its presence is observable end-to-end.
28
+ - ``on_deliverable`` prints the deliverable body to the worker's
29
+ terminal — additive to the framework's auto-emitted
30
+ ``"Deliverable: <path>"`` progress message.
31
+ - ``after_execute`` reads ``./optio.log`` back via
32
+ ``read_text_from_host`` and reports a one-line summary, proving the
33
+ hook fires before workdir teardown.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import os
39
+ from datetime import datetime, timezone
40
+
41
+ from optio_claudecode import (
42
+ ClaudeCodeTaskConfig,
43
+ HookContext,
44
+ SSHConfig,
45
+ create_claudecode_task,
46
+ )
47
+ from optio_core.models import TaskInstance
48
+
49
+
50
+ CONTEXT_TXT = b"""\
51
+ Mission code-name: Project Petunia
52
+ Authorized color: turquoise
53
+ """
54
+
55
+
56
+ CONSUMER_PROMPT = (
57
+ "First, read the file `./context.txt` in your working directory. It "
58
+ "contains a mission code-name and an authorized color. Then ask the "
59
+ "human about their favorite color. Ship a deliverable file at "
60
+ "`./deliverables/mission-report.txt` containing the mission "
61
+ "code-name, the authorized color, the human's favorite color, and "
62
+ "the number 42. Then signal completion by appending a `DONE` line "
63
+ "to the `./optio.log` file (writing `DONE` in the chat has no "
64
+ "effect — it must go into that file)."
65
+ )
66
+
67
+
68
+ DEMO_SEED_COLLECTION_SUFFIX = "_demo_claude_seeds"
69
+
70
+ SEED_SETUP_PROMPT = (
71
+ "This is a one-time setup session. Use the terminal to log into "
72
+ "Claude Code (run `/login` and follow the prompts) and install any "
73
+ "plugins or MCP servers you want available to demo tasks. When you "
74
+ "are done, STOP this task from the dashboard — your configuration "
75
+ "(credentials, settings, plugins) will be captured as a reusable "
76
+ "seed, and a new 'Claude Code demo' task pinned to that seed will "
77
+ "appear automatically."
78
+ )
79
+
80
+
81
+ def _resolve_ssh_config() -> SSHConfig | None:
82
+ host = os.environ.get("OPTIO_CLAUDECODE_DEMO_SSH_HOST")
83
+ if not host:
84
+ return None
85
+ user = (
86
+ os.environ.get("OPTIO_CLAUDECODE_DEMO_SSH_USER")
87
+ or os.environ.get("USER")
88
+ or "root"
89
+ )
90
+ key_path = os.environ.get(
91
+ "OPTIO_CLAUDECODE_DEMO_SSH_KEY_PATH",
92
+ os.path.expanduser("~/.ssh/id_ed25519"),
93
+ )
94
+ port_raw = os.environ.get("OPTIO_CLAUDECODE_DEMO_SSH_PORT", "22")
95
+ try:
96
+ port = int(port_raw)
97
+ except ValueError:
98
+ raise RuntimeError(
99
+ f"OPTIO_CLAUDECODE_DEMO_SSH_PORT must be an integer, got {port_raw!r}"
100
+ )
101
+ return SSHConfig(host=host, user=user, key_path=key_path, port=port)
102
+
103
+
104
+ async def _before_execute(hook_ctx: HookContext) -> None:
105
+ out = await hook_ctx.run_on_host("whoami")
106
+ hook_ctx.report_progress(None, f"claude will run as {out.strip()}")
107
+ await hook_ctx.copy_file(CONTEXT_TXT, "context.txt")
108
+
109
+
110
+ async def _on_deliverable(
111
+ hook_ctx: HookContext, path: str, text: str,
112
+ ) -> None:
113
+ print(f"[claudecode-demo] deliverable {path}:\n{text}")
114
+
115
+
116
+ async def _after_execute(hook_ctx: HookContext) -> None:
117
+ try:
118
+ log = await hook_ctx.read_text_from_host("optio.log")
119
+ except FileNotFoundError:
120
+ hook_ctx.report_progress(None, "session log: not present")
121
+ return
122
+ lines = log.splitlines()
123
+ counts = {"STATUS": 0, "DELIVERABLE": 0, "DONE": 0, "ERROR": 0}
124
+ for line in lines:
125
+ for keyword in counts:
126
+ if line.startswith(keyword):
127
+ counts[keyword] += 1
128
+ break
129
+ summary = ", ".join(f"{n} {k}" for k, n in counts.items() if n)
130
+ hook_ctx.report_progress(
131
+ None,
132
+ f"session log: {len(lines)} lines ({summary or 'no keywords'})",
133
+ )
134
+
135
+
136
+ def _make_on_seed_saved(db, prefix: str, fw):
137
+ coll = db[f"{prefix}{DEMO_SEED_COLLECTION_SUFFIX}"]
138
+
139
+ async def _on_seed_saved(seed_id: str) -> None:
140
+ # Cosmetic numbering; a concurrent-save race may reuse a number —
141
+ # acceptable, the seedId is the real key.
142
+ count = await coll.count_documents({})
143
+ name = f"Config #{count + 1}"
144
+ await coll.insert_one({
145
+ "seedId": seed_id,
146
+ "name": name,
147
+ "createdAt": datetime.now(timezone.utc),
148
+ })
149
+ # Regenerate the task list so a seed-pinned demo task appears.
150
+ await fw.resync()
151
+
152
+ return _on_seed_saved
153
+
154
+
155
+ async def get_tasks(services: dict) -> list[TaskInstance]:
156
+ db = services["db"]
157
+ prefix = services["prefix"]
158
+ fw = services["optio"]
159
+ ssh = _resolve_ssh_config()
160
+
161
+ tasks: list[TaskInstance] = [
162
+ # The seed setup task: vanilla (no seed_id), on_seed_saved wired.
163
+ create_claudecode_task(
164
+ process_id="claudecode-seed-setup",
165
+ name="Setup Claude Code seed",
166
+ description=(
167
+ "One-time: log into Claude Code and configure plugins, "
168
+ "then stop the task to capture a reusable seed. A new "
169
+ "seed-pinned demo task appears afterward."
170
+ ),
171
+ config=ClaudeCodeTaskConfig(
172
+ consumer_instructions=SEED_SETUP_PROMPT,
173
+ ssh=ssh,
174
+ # Run setup in bypassPermissions so the operator accepts the
175
+ # bypass-mode warning once here; the acknowledgment lands in
176
+ # ~/.claude.json and is captured into the seed, so every
177
+ # seed-launched demo task (also bypassPermissions) starts
178
+ # pre-acked instead of warning on each launch.
179
+ permission_mode="bypassPermissions",
180
+ # Interactive login; no resume for a one-time setup session.
181
+ supports_resume=False,
182
+ on_seed_saved=_make_on_seed_saved(db, prefix, fw),
183
+ ),
184
+ ),
185
+ ]
186
+
187
+ # One seed-pinned demo task per recorded seed.
188
+ coll = db[f"{prefix}{DEMO_SEED_COLLECTION_SUFFIX}"]
189
+ async for rec in coll.find({}, projection={"seedId": 1, "name": 1}):
190
+ seed_id = rec["seedId"]
191
+ name = rec.get("name", seed_id)
192
+ tasks.append(
193
+ create_claudecode_task(
194
+ process_id=f"claudecode-demo-seed-{seed_id}",
195
+ name=f"Claude Code demo — {name}",
196
+ description=(
197
+ "Fresh Claude Code session started from a captured "
198
+ f"seed ({name}): logged-in and configured, new "
199
+ "conversation. Reads context.txt, asks for a color, "
200
+ "ships a deliverable."
201
+ ),
202
+ config=ClaudeCodeTaskConfig(
203
+ consumer_instructions=CONSUMER_PROMPT,
204
+ permission_mode="bypassPermissions",
205
+ ssh=ssh,
206
+ before_execute=_before_execute,
207
+ after_execute=_after_execute,
208
+ on_deliverable=_on_deliverable,
209
+ seed_id=seed_id,
210
+ supports_resume=True,
211
+ ),
212
+ )
213
+ )
214
+
215
+ return tasks
@@ -0,0 +1,128 @@
1
+ """Client-directed events demo tasks (phase 2).
2
+
3
+ Four tasks exercising the three new capabilities end-to-end:
4
+
5
+ - ``open-optio-repo``: pure-Python ``ctx.request_browser_open`` (view-scoped).
6
+ - ``open-browser-via-tool``: a host task that runs a tiny Python script
7
+ (``import webbrowser; webbrowser.open(URL)`` then ``DONE``) through the
8
+ optio-agents session driver with ``get_protocol(browser="redirect")`` —
9
+ shim → ``BROWSER:`` marker → parser → ``ctx.request_browser_open`` with no
10
+ claude/opencode involved.
11
+ - ``need-attention-demo``: ``ctx.need_attention`` (session-scoped).
12
+ - ``domain-message-demo``: ``ctx.domain_message`` (session-scoped).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import os
19
+
20
+ from optio_core.models import TaskInstance
21
+ from optio_host.host import LocalHost
22
+ from optio_agents import run_log_protocol_session, get_protocol
23
+
24
+
25
+ OPTIO_REPO_URL = "https://github.com/deai-network/optio"
26
+
27
+
28
+ async def _open_optio_repo(ctx) -> None:
29
+ ctx.report_progress(0, "Opening the optio repo in your browser")
30
+ rid = await ctx.request_browser_open(OPTIO_REPO_URL)
31
+ ctx.report_progress(100, f"Requested browser open (requestId={rid})")
32
+
33
+
34
+ async def _open_browser_via_tool(ctx) -> None:
35
+ """Host-bridge capture test: run a Python opener under capture shims."""
36
+ taskdir = f"/tmp/optio-demo-browser-{os.getpid()}-{ctx.process_id}"
37
+ os.makedirs(taskdir, exist_ok=True)
38
+ host = LocalHost(taskdir=taskdir)
39
+ os.makedirs(host.workdir, exist_ok=True)
40
+
41
+ async def body(host, hook_ctx) -> None:
42
+ # The driver installed the redirect (capture) shims before the body
43
+ # ran and exposed their env on hook_ctx.browser_launch_env. A trivial
44
+ # opener: webbrowser.open routes through xdg-open (the shim), which
45
+ # appends the BROWSER: marker to optio.log. Then signal DONE.
46
+ script = (
47
+ "import webbrowser; "
48
+ f"webbrowser.open({OPTIO_REPO_URL!r}); "
49
+ )
50
+ await host.run_command(
51
+ f"python3 -c {script!r}",
52
+ env=hook_ctx.browser_launch_env,
53
+ cwd=host.workdir,
54
+ )
55
+ # The shim has appended BROWSER: by now; close out the session.
56
+ await host.run_command(f"echo DONE >> {host.workdir}/optio.log")
57
+
58
+ await run_log_protocol_session(
59
+ host, ctx, body=body, protocol=get_protocol(browser="redirect"),
60
+ )
61
+
62
+
63
+ async def _need_attention_demo(ctx) -> None:
64
+ # Delay before asking, so the operator can navigate away to another task
65
+ # and observe the session-scoped attention request pull them back here.
66
+ DELAY = 10
67
+ for elapsed in range(DELAY):
68
+ ctx.report_progress(
69
+ int(100 * elapsed / DELAY),
70
+ f"Working… will request attention in {DELAY - elapsed}s "
71
+ f"(navigate away to test it)",
72
+ )
73
+ await asyncio.sleep(1)
74
+ rid = await ctx.need_attention("The demo task would like you to look at it.")
75
+ ctx.report_progress(100, f"Attention requested (requestId={rid})")
76
+
77
+
78
+ async def _domain_message_demo(ctx) -> None:
79
+ ctx.report_progress(0, "Sending a domain message")
80
+ rid = await ctx.domain_message(
81
+ "demo-event",
82
+ {"severity": "info", "detail": "hello from the demo task"},
83
+ )
84
+ ctx.report_progress(100, f"Domain message sent (requestId={rid})")
85
+
86
+
87
+ def get_tasks() -> list[TaskInstance]:
88
+ return [
89
+ TaskInstance(
90
+ execute=_open_optio_repo,
91
+ process_id="open-optio-repo",
92
+ name="Open the optio repo",
93
+ description=(
94
+ "Pure-Python browser_open: asks your browser to open the optio "
95
+ "GitHub repo. View-scoped — delivered to whoever is watching."
96
+ ),
97
+ ),
98
+ TaskInstance(
99
+ execute=_open_browser_via_tool,
100
+ process_id="open-browser-via-tool",
101
+ name="Open browser via tool (capture bridge)",
102
+ description=(
103
+ "Host task running a Python webbrowser.open under "
104
+ "get_protocol(redirect) capture shims; exercises shim → "
105
+ "BROWSER: marker → parser → ctx.request_browser_open "
106
+ "end-to-end (no agent)."
107
+ ),
108
+ ),
109
+ TaskInstance(
110
+ execute=_need_attention_demo,
111
+ process_id="need-attention-demo",
112
+ name="Request attention",
113
+ description=(
114
+ "Calls ctx.need_attention(...). Session-scoped — reaches the "
115
+ "browser session that launched it. The dashboard navigates to "
116
+ "this process via onAttention."
117
+ ),
118
+ ),
119
+ TaskInstance(
120
+ execute=_domain_message_demo,
121
+ process_id="domain-message-demo",
122
+ name="Send a domain message",
123
+ description=(
124
+ "Calls ctx.domain_message(keyword, data). Session-scoped; the "
125
+ "dashboard surfaces it via onDomainMessage (console/toast)."
126
+ ),
127
+ ),
128
+ ]