openconstruct-jupyter 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.
- openconstruct_jupyter-0.1.0/PKG-INFO +91 -0
- openconstruct_jupyter-0.1.0/README.md +71 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter/__init__.py +59 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter/display.py +86 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter/fleet_panel.py +38 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter/kernel.py +170 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter/magic.py +81 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter/room_viewer.py +44 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter/widgets.py +134 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter.egg-info/PKG-INFO +91 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter.egg-info/SOURCES.txt +16 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter.egg-info/dependency_links.txt +1 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter.egg-info/entry_points.txt +2 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter.egg-info/requires.txt +7 -0
- openconstruct_jupyter-0.1.0/openconstruct_jupyter.egg-info/top_level.txt +1 -0
- openconstruct_jupyter-0.1.0/pyproject.toml +33 -0
- openconstruct_jupyter-0.1.0/setup.cfg +4 -0
- openconstruct_jupyter-0.1.0/tests/test_magic.py +181 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openconstruct-jupyter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Jupyter notebook integration for OpenConstruct — connect, sense, and build with agents
|
|
5
|
+
Author: SuperInstance
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Framework :: Jupyter
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: ipython>=7.0
|
|
15
|
+
Requires-Dist: ipywidgets>=8.0
|
|
16
|
+
Requires-Dist: rich>=13.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
20
|
+
|
|
21
|
+
# openconstruct-jupyter — Jupyter Integration for OpenConstruct
|
|
22
|
+
|
|
23
|
+
Connect OpenConstruct to your Jupyter notebooks in one line. Cell magic, widgets, fleet panels, and DataFrame-to-room conversion.
|
|
24
|
+
|
|
25
|
+
**Part of [SuperInstance OpenConstruct](https://github.com/SuperInstance/OpenConstruct).**
|
|
26
|
+
|
|
27
|
+
## What This Gives You
|
|
28
|
+
|
|
29
|
+
- **Cell magic** — `%%openconstruct --sense vision` captures output as a sense shadow
|
|
30
|
+
- **DataFrame → Plato room** — turn any pandas DataFrame into a knowledge graph
|
|
31
|
+
- **Python function → agent tool** — `oc.from_function(greet, name="my_tool")`
|
|
32
|
+
- **Fleet panel** — inline widget showing discovered fleet nodes
|
|
33
|
+
- **Room viewer** — visualize Plato room tiles and dependencies
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# Load the extension
|
|
39
|
+
%load_ext openconstruct_jupyter
|
|
40
|
+
|
|
41
|
+
# Connect
|
|
42
|
+
import openconstruct_jupyter as oc
|
|
43
|
+
oc.connect(notebook=True)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Cell Magic
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
%%openconstruct --sense vision
|
|
50
|
+
# Cell output captured as a vision shadow
|
|
51
|
+
|
|
52
|
+
%%openconstruct --tick "analysis complete"
|
|
53
|
+
# Post a tick to the fleet
|
|
54
|
+
|
|
55
|
+
%%openconstruct --room my-analysis
|
|
56
|
+
# Create or load a Plato room
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Python API
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import pandas as pd
|
|
63
|
+
|
|
64
|
+
# DataFrame → room
|
|
65
|
+
df = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
|
|
66
|
+
oc.from_dataframe(df, name="my_data")
|
|
67
|
+
|
|
68
|
+
# Function → agent tool
|
|
69
|
+
def greet(name):
|
|
70
|
+
return f"Hello, {name}!"
|
|
71
|
+
oc.from_function(greet, name="greet_tool")
|
|
72
|
+
|
|
73
|
+
# REST API → agent resource
|
|
74
|
+
oc.from_api("https://api.example.com/data", name="external_data")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install openconstruct-jupyter
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Testing
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pytest tests/ -v
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# openconstruct-jupyter — Jupyter Integration for OpenConstruct
|
|
2
|
+
|
|
3
|
+
Connect OpenConstruct to your Jupyter notebooks in one line. Cell magic, widgets, fleet panels, and DataFrame-to-room conversion.
|
|
4
|
+
|
|
5
|
+
**Part of [SuperInstance OpenConstruct](https://github.com/SuperInstance/OpenConstruct).**
|
|
6
|
+
|
|
7
|
+
## What This Gives You
|
|
8
|
+
|
|
9
|
+
- **Cell magic** — `%%openconstruct --sense vision` captures output as a sense shadow
|
|
10
|
+
- **DataFrame → Plato room** — turn any pandas DataFrame into a knowledge graph
|
|
11
|
+
- **Python function → agent tool** — `oc.from_function(greet, name="my_tool")`
|
|
12
|
+
- **Fleet panel** — inline widget showing discovered fleet nodes
|
|
13
|
+
- **Room viewer** — visualize Plato room tiles and dependencies
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
# Load the extension
|
|
19
|
+
%load_ext openconstruct_jupyter
|
|
20
|
+
|
|
21
|
+
# Connect
|
|
22
|
+
import openconstruct_jupyter as oc
|
|
23
|
+
oc.connect(notebook=True)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Cell Magic
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
%%openconstruct --sense vision
|
|
30
|
+
# Cell output captured as a vision shadow
|
|
31
|
+
|
|
32
|
+
%%openconstruct --tick "analysis complete"
|
|
33
|
+
# Post a tick to the fleet
|
|
34
|
+
|
|
35
|
+
%%openconstruct --room my-analysis
|
|
36
|
+
# Create or load a Plato room
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Python API
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import pandas as pd
|
|
43
|
+
|
|
44
|
+
# DataFrame → room
|
|
45
|
+
df = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
|
|
46
|
+
oc.from_dataframe(df, name="my_data")
|
|
47
|
+
|
|
48
|
+
# Function → agent tool
|
|
49
|
+
def greet(name):
|
|
50
|
+
return f"Hello, {name}!"
|
|
51
|
+
oc.from_function(greet, name="greet_tool")
|
|
52
|
+
|
|
53
|
+
# REST API → agent resource
|
|
54
|
+
oc.from_api("https://api.example.com/data", name="external_data")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install openconstruct-jupyter
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Testing
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pytest tests/ -v
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openconstruct-jupyter — Jupyter notebook integration for OpenConstruct.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
|
|
7
|
+
from .magic import load_ipython_extension, unload_ipython_extension
|
|
8
|
+
from .widgets import (
|
|
9
|
+
AgentConfigWidget,
|
|
10
|
+
FleetStatusWidget,
|
|
11
|
+
SenseShadowWidget,
|
|
12
|
+
RoomGraphWidget,
|
|
13
|
+
TickBoardWidget,
|
|
14
|
+
PolicyWidget,
|
|
15
|
+
)
|
|
16
|
+
from .kernel import OCClient
|
|
17
|
+
|
|
18
|
+
# Module-level convenience functions backed by a default OCClient
|
|
19
|
+
_default_client: OCClient | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_client() -> OCClient:
|
|
23
|
+
global _default_client
|
|
24
|
+
if _default_client is None:
|
|
25
|
+
_default_client = OCClient()
|
|
26
|
+
return _default_client
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def connect(**kwargs) -> OCClient:
|
|
30
|
+
"""Connect this notebook to OpenConstruct. Returns the active client."""
|
|
31
|
+
global _default_client
|
|
32
|
+
_default_client = OCClient(**kwargs)
|
|
33
|
+
_default_client.connect()
|
|
34
|
+
return _default_client
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def from_dataframe(df, *, name: str = "data"):
|
|
38
|
+
"""Convert a DataFrame into a Plato room."""
|
|
39
|
+
return _get_client().from_dataframe(df, name=name)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def from_function(func, *, name: str = "tool"):
|
|
43
|
+
"""Wrap a Python function as an agent tool."""
|
|
44
|
+
return _get_client().from_function(func, name=name)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def from_model(model, *, name: str = "model"):
|
|
48
|
+
"""Wrap an ML model as a sense module."""
|
|
49
|
+
return _get_client().from_model(model, name=name)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def from_api(url: str, *, name: str = "api"):
|
|
53
|
+
"""Register a REST API as an agent resource."""
|
|
54
|
+
return _get_client().from_api(url, name=name)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def publish_room():
|
|
58
|
+
"""Publish the notebook's room with the fleet."""
|
|
59
|
+
return _get_client().publish_room()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rich display rendering for sense shadows, fleet topology, rooms, ticks, and config.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import html as html_lib
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def render_fleet(fleet_data: list[dict]) -> str:
|
|
12
|
+
"""Render fleet topology as a styled HTML table."""
|
|
13
|
+
if not fleet_data:
|
|
14
|
+
return "<div style='color:#888'>No fleet agents discovered.</div>"
|
|
15
|
+
rows = "".join(
|
|
16
|
+
f"<tr><td>{a['agent_id']}</td><td><b>{a['name']}</b></td>"
|
|
17
|
+
f"<td style='color:{'#4a9' if a['status']=='online' else '#a44'}'>{a['status']}</td>"
|
|
18
|
+
f"<td>{', '.join(a.get('modules', []))}</td></tr>"
|
|
19
|
+
for a in fleet_data
|
|
20
|
+
)
|
|
21
|
+
return (
|
|
22
|
+
"<div style='font-family:monospace;background:#1e1e1e;color:#ddd;padding:12px;border-radius:6px'>"
|
|
23
|
+
"<b>🏗 Fleet Topology</b><br><br>"
|
|
24
|
+
"<table style='border-collapse:collapse'>"
|
|
25
|
+
"<tr><th style='padding:4px 12px;border-bottom:1px solid #555'>ID</th>"
|
|
26
|
+
"<th style='padding:4px 12px;border-bottom:1px solid #555'>Name</th>"
|
|
27
|
+
"<th style='padding:4px 12px;border-bottom:1px solid #555'>Status</th>"
|
|
28
|
+
"<th style='padding:4px 12px;border-bottom:1px solid #555'>Modules</th></tr>"
|
|
29
|
+
f"{rows}</table></div>"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def render_room(room_data: dict) -> str:
|
|
34
|
+
"""Render a Plato room as an SVG-style node graph."""
|
|
35
|
+
name = html_lib.escape(room_data.get("name", "?"))
|
|
36
|
+
tiles = room_data.get("tiles", [])
|
|
37
|
+
nodes_html = "".join(
|
|
38
|
+
f'<div style="border:1px solid #4a9;padding:4px 10px;margin:3px;border-radius:6px;'
|
|
39
|
+
f'background:#1a2a1a;display:inline-block">'
|
|
40
|
+
f'{html_lib.escape(t["name"])} <small style="color:#8ac">CR {t.get("cr_score", 0)}</small></div>'
|
|
41
|
+
for t in tiles
|
|
42
|
+
)
|
|
43
|
+
return (
|
|
44
|
+
f"<div style='font-family:monospace;background:#1a1a2a;color:#ddd;padding:12px;border-radius:6px'>"
|
|
45
|
+
f"<b>🏠 Room: {name}</b> <small>({len(tiles)} tiles)</small><br><br>"
|
|
46
|
+
f"{nodes_html}</div>"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def render_tick(message: str) -> str:
|
|
51
|
+
"""Render a tick as a chat bubble."""
|
|
52
|
+
safe = html_lib.escape(message)
|
|
53
|
+
return (
|
|
54
|
+
f"<div style='background:#2a3a5a;color:#ddd;padding:8px 14px;margin:4px 0;"
|
|
55
|
+
f"border-radius:10px;max-width:75%;font-family:monospace;display:inline-block'>"
|
|
56
|
+
f"✅ {safe}</div><br>"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def render_shadow(shadow) -> str:
|
|
61
|
+
"""Render a sense shadow as styled HTML."""
|
|
62
|
+
sense_type = getattr(shadow, "sense_type", "text")
|
|
63
|
+
output = html_lib.escape(str(getattr(shadow, "output", "")))
|
|
64
|
+
status = "✓" if getattr(shadow, "success", True) else "✗"
|
|
65
|
+
color = "#4a9" if getattr(shadow, "success", True) else "#a44"
|
|
66
|
+
return (
|
|
67
|
+
f"<div style='font-family:monospace;background:#1a1a1a;color:#ddd;padding:10px;"
|
|
68
|
+
f"border-left:3px solid {color};border-radius:4px;margin:4px 0'>"
|
|
69
|
+
f"<b>🔍 Sense: {html_lib.escape(sense_type)}</b> {status}<br>"
|
|
70
|
+
f"<pre style='margin:4px 0;color:#aaa'>{output[:500]}</pre></div>"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def render_config_yaml(config: dict) -> str:
|
|
75
|
+
"""Render config as syntax-highlighted YAML-ish HTML."""
|
|
76
|
+
lines = []
|
|
77
|
+
for k, v in config.items():
|
|
78
|
+
if isinstance(v, bool):
|
|
79
|
+
color = "#4a9" if v else "#a44"
|
|
80
|
+
lines.append(f'<span style="color:#88f">{k}</span>: <span style="color:{color}">{v}</span>')
|
|
81
|
+
elif isinstance(v, str):
|
|
82
|
+
lines.append(f'<span style="color:#88f">{k}</span>: <span style="color:#aa4">"{html_lib.escape(v)}"</span>')
|
|
83
|
+
else:
|
|
84
|
+
lines.append(f'<span style="color:#88f">{k}</span>: <span style="color:#ddd">{v}</span>')
|
|
85
|
+
body = "<br>".join(lines)
|
|
86
|
+
return f"<div style='font-family:monospace;background:#1e1e1e;color:#ddd;padding:10px;border-radius:4px'>{body}</div>"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fleet status dashboard widget — a higher-level convenience wrapper.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ipywidgets as widgets
|
|
8
|
+
from IPython.display import display
|
|
9
|
+
|
|
10
|
+
from .kernel import OCClient
|
|
11
|
+
from .display import render_fleet
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FleetPanel(widgets.VBox):
|
|
15
|
+
"""Full fleet status dashboard with auto-refresh capability."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, client: OCClient | None = None, **kwargs):
|
|
18
|
+
self._client = client or OCClient()
|
|
19
|
+
self.header = widgets.HTML(value="<h3>🏗 OpenConstruct Fleet</h3>")
|
|
20
|
+
self.body = widgets.HTML(value="<i>Click Refresh to discover fleet agents.</i>")
|
|
21
|
+
self.refresh_btn = widgets.Button(
|
|
22
|
+
description="🔄 Refresh", button_style="info", layout=widgets.Layout(width="120px")
|
|
23
|
+
)
|
|
24
|
+
self.refresh_btn.on_click(self._refresh)
|
|
25
|
+
self.connect_btn = widgets.Button(
|
|
26
|
+
description="⚡ Connect", button_style="success", layout=widgets.Layout(width="120px")
|
|
27
|
+
)
|
|
28
|
+
self.connect_btn.on_click(self._connect)
|
|
29
|
+
button_row = widgets.HBox(children=[self.connect_btn, self.refresh_btn])
|
|
30
|
+
super().__init__(children=[self.header, button_row, self.body], **kwargs)
|
|
31
|
+
|
|
32
|
+
def _connect(self, btn):
|
|
33
|
+
self._client.connect()
|
|
34
|
+
self._refresh(btn)
|
|
35
|
+
|
|
36
|
+
def _refresh(self, btn):
|
|
37
|
+
fleet = self._client.discover_fleet()
|
|
38
|
+
self.body.value = render_fleet(fleet)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent-aware kernel hooks and OCClient — the brain behind the magic.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any, Callable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class SenseShadow:
|
|
16
|
+
"""A captured sense shadow."""
|
|
17
|
+
shadow_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
|
|
18
|
+
sense_type: str = "text"
|
|
19
|
+
output: str = ""
|
|
20
|
+
success: bool = True
|
|
21
|
+
timestamp: float = field(default_factory=time.time)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class PlatoTile:
|
|
26
|
+
"""A tile in a Plato room."""
|
|
27
|
+
tile_id: str = field(default_factory=lambda: uuid.uuid4().hex[:8])
|
|
28
|
+
name: str = ""
|
|
29
|
+
value: Any = None
|
|
30
|
+
cr_score: float = 0.0
|
|
31
|
+
source: str = "notebook"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class PlatoRoom:
|
|
36
|
+
"""A Plato room — a shared knowledge space."""
|
|
37
|
+
room_id: str = field(default_factory=lambda: uuid.uuid4().hex[:8])
|
|
38
|
+
name: str = "default"
|
|
39
|
+
tiles: list[PlatoTile] = field(default_factory=list)
|
|
40
|
+
created: float = field(default_factory=time.time)
|
|
41
|
+
|
|
42
|
+
def add_tile(self, tile: PlatoTile):
|
|
43
|
+
self.tiles.append(tile)
|
|
44
|
+
|
|
45
|
+
def to_dict(self) -> dict:
|
|
46
|
+
return {
|
|
47
|
+
"room_id": self.room_id,
|
|
48
|
+
"name": self.name,
|
|
49
|
+
"tiles": [
|
|
50
|
+
{"tile_id": t.tile_id, "name": t.name, "cr_score": t.cr_score, "source": t.source}
|
|
51
|
+
for t in self.tiles
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class FleetAgent:
|
|
58
|
+
"""An agent in the fleet."""
|
|
59
|
+
agent_id: str = ""
|
|
60
|
+
name: str = ""
|
|
61
|
+
status: str = "online"
|
|
62
|
+
modules: list[str] = field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Tick:
|
|
67
|
+
"""A tick message."""
|
|
68
|
+
tick_id: str = field(default_factory=lambda: uuid.uuid4().hex[:8])
|
|
69
|
+
message: str = ""
|
|
70
|
+
timestamp: float = field(default_factory=time.time)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class OCClient:
|
|
74
|
+
"""
|
|
75
|
+
The core OpenConstruct client for Jupyter notebooks.
|
|
76
|
+
|
|
77
|
+
Maintains local state (rooms, shadows, fleet) and provides
|
|
78
|
+
the Python API surface (from_dataframe, from_function, etc.).
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, notebook: bool = False, **kwargs):
|
|
82
|
+
self.connected = False
|
|
83
|
+
self.notebook = notebook
|
|
84
|
+
self.shadows: list[SenseShadow] = []
|
|
85
|
+
self.rooms: dict[str, PlatoRoom] = {}
|
|
86
|
+
self.fleet: list[FleetAgent] = []
|
|
87
|
+
self.ticks: list[Tick] = []
|
|
88
|
+
self.tools: dict[str, Callable] = {}
|
|
89
|
+
self.api_resources: dict[str, dict] = {}
|
|
90
|
+
self._default_room = PlatoRoom(name="notebook")
|
|
91
|
+
|
|
92
|
+
# --- Connection ---
|
|
93
|
+
|
|
94
|
+
def connect(self) -> dict:
|
|
95
|
+
"""Connect to the OpenConstruct network."""
|
|
96
|
+
self.connected = True
|
|
97
|
+
self.fleet = [
|
|
98
|
+
FleetAgent(agent_id="self", name="notebook-agent", status="online", modules=["vision", "text"]),
|
|
99
|
+
]
|
|
100
|
+
return {"status": "connected", "agent_id": "self"}
|
|
101
|
+
|
|
102
|
+
# --- Fleet ---
|
|
103
|
+
|
|
104
|
+
def discover_fleet(self) -> list[dict]:
|
|
105
|
+
"""Return fleet topology."""
|
|
106
|
+
return [
|
|
107
|
+
{"agent_id": a.agent_id, "name": a.name, "status": a.status, "modules": a.modules}
|
|
108
|
+
for a in self.fleet
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
# --- Shadows ---
|
|
112
|
+
|
|
113
|
+
def register_shadow(self, sense_type: str, output: str, success: bool = True) -> SenseShadow:
|
|
114
|
+
shadow = SenseShadow(sense_type=sense_type, output=output, success=success)
|
|
115
|
+
self.shadows.append(shadow)
|
|
116
|
+
return shadow
|
|
117
|
+
|
|
118
|
+
# --- Rooms ---
|
|
119
|
+
|
|
120
|
+
def load_room(self, name: str, context: str = "") -> PlatoRoom:
|
|
121
|
+
if name not in self.rooms:
|
|
122
|
+
self.rooms[name] = PlatoRoom(name=name)
|
|
123
|
+
if context:
|
|
124
|
+
tile = PlatoTile(name="cell_context", value=context, cr_score=0.5)
|
|
125
|
+
self.rooms[name].add_tile(tile)
|
|
126
|
+
return self.rooms[name]
|
|
127
|
+
|
|
128
|
+
def publish_room(self) -> dict:
|
|
129
|
+
"""Publish the default room to the fleet."""
|
|
130
|
+
self._default_room.name = "published-notebook"
|
|
131
|
+
return self._default_room.to_dict()
|
|
132
|
+
|
|
133
|
+
# --- Ticks ---
|
|
134
|
+
|
|
135
|
+
def post_tick(self, message: str) -> Tick:
|
|
136
|
+
tick = Tick(message=message)
|
|
137
|
+
self.ticks.append(tick)
|
|
138
|
+
return tick
|
|
139
|
+
|
|
140
|
+
# --- Integration helpers ---
|
|
141
|
+
|
|
142
|
+
def from_dataframe(self, df, *, name: str = "data") -> PlatoTile:
|
|
143
|
+
"""Convert a DataFrame into a Plato room tile with CR score."""
|
|
144
|
+
try:
|
|
145
|
+
rows = len(df)
|
|
146
|
+
cols = len(df.columns)
|
|
147
|
+
cr = min(1.0, (rows * cols) / 1000)
|
|
148
|
+
except Exception:
|
|
149
|
+
cr = 0.1
|
|
150
|
+
tile = PlatoTile(name=name, value=df, cr_score=round(cr, 2), source="dataframe")
|
|
151
|
+
self._default_room.add_tile(tile)
|
|
152
|
+
return tile
|
|
153
|
+
|
|
154
|
+
def from_function(self, func: Callable, *, name: str = "tool") -> dict:
|
|
155
|
+
"""Register a Python function as an agent tool."""
|
|
156
|
+
self.tools[name] = func
|
|
157
|
+
return {"name": name, "type": "function", "registered": True}
|
|
158
|
+
|
|
159
|
+
def from_model(self, model, *, name: str = "model") -> dict:
|
|
160
|
+
"""Register an ML model as a sense module."""
|
|
161
|
+
self.fleet.append(
|
|
162
|
+
FleetAgent(agent_id=name, name=name, status="online", modules=["inference"])
|
|
163
|
+
)
|
|
164
|
+
return {"name": name, "type": "model", "registered": True}
|
|
165
|
+
|
|
166
|
+
def from_api(self, url: str, *, name: str = "api") -> dict:
|
|
167
|
+
"""Register a REST API as an agent resource."""
|
|
168
|
+
entry = {"url": url, "name": name, "type": "api"}
|
|
169
|
+
self.api_resources[name] = entry
|
|
170
|
+
return entry
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
%%openconstruct cell magic for IPython / Jupyter.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from IPython.core.magic import Magics, magics_class, cell_magic, line_magic
|
|
13
|
+
from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring
|
|
14
|
+
from IPython.display import display, HTML, Javascript
|
|
15
|
+
|
|
16
|
+
from .kernel import OCClient
|
|
17
|
+
from .display import render_fleet, render_room, render_tick, render_shadow
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@magics_class
|
|
21
|
+
class OpenConstructMagics(Magics):
|
|
22
|
+
"""IPython magics for OpenConstruct."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, shell=None, **kwargs):
|
|
25
|
+
super().__init__(shell, **kwargs)
|
|
26
|
+
self._client: OCClient | None = None
|
|
27
|
+
|
|
28
|
+
def _get_client(self) -> OCClient:
|
|
29
|
+
if self._client is None:
|
|
30
|
+
self._client = OCClient()
|
|
31
|
+
return self._client
|
|
32
|
+
|
|
33
|
+
@magic_arguments()
|
|
34
|
+
@argument("--sense", type=str, default=None, help="Capture cell output as a sense shadow (e.g. vision, text)")
|
|
35
|
+
@argument("--tick", type=str, default=None, help="Post a tick message")
|
|
36
|
+
@argument("--room", type=str, default=None, help="Create or load a Plato room")
|
|
37
|
+
@argument("--fleet", action="store_true", help="Show fleet discovery inline")
|
|
38
|
+
@cell_magic
|
|
39
|
+
def openconstruct(self, line: str, cell: str):
|
|
40
|
+
"""OpenConstruct cell magic."""
|
|
41
|
+
args = parse_argstring(self.openconstruct, line)
|
|
42
|
+
client = self._get_client()
|
|
43
|
+
|
|
44
|
+
if args.fleet:
|
|
45
|
+
fleet_data = client.discover_fleet()
|
|
46
|
+
display(HTML(render_fleet(fleet_data)))
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if args.tick:
|
|
50
|
+
client.post_tick(args.tick)
|
|
51
|
+
display(HTML(render_tick(args.tick)))
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if args.room:
|
|
55
|
+
room_data = client.load_room(args.room, context=cell)
|
|
56
|
+
display(HTML(render_room(room_data)))
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
if args.sense:
|
|
60
|
+
# Execute cell, capture output as a sense shadow
|
|
61
|
+
result = self.shell.run_cell(cell)
|
|
62
|
+
shadow = client.register_shadow(
|
|
63
|
+
sense_type=args.sense,
|
|
64
|
+
output=str(result.result) if result.result else "",
|
|
65
|
+
success=result.success,
|
|
66
|
+
)
|
|
67
|
+
display(HTML(render_shadow(shadow)))
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
# Default: execute and show raw output
|
|
71
|
+
self.shell.run_cell(cell)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def load_ipython_extension(ipython):
|
|
75
|
+
"""Register the OpenConstruct magics with IPython."""
|
|
76
|
+
ipython.register_magics(OpenConstructMagics)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def unload_ipython_extension(ipython):
|
|
80
|
+
"""Unregister the OpenConstruct magics (no-op, IPython handles it)."""
|
|
81
|
+
pass
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plato Room visualizer widget.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ipywidgets as widgets
|
|
8
|
+
from IPython.display import display
|
|
9
|
+
|
|
10
|
+
from .kernel import OCClient, PlatoRoom
|
|
11
|
+
from .display import render_room
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RoomViewer(widgets.VBox):
|
|
15
|
+
"""Interactive Plato Room visualizer."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, client: OCClient | None = None, **kwargs):
|
|
18
|
+
self._client = client or OCClient()
|
|
19
|
+
self.header = widgets.HTML(value="<h3>🏠 Plato Room Viewer</h3>")
|
|
20
|
+
self.room_select = widgets.Dropdown(
|
|
21
|
+
options=["(no rooms)"],
|
|
22
|
+
description="Room:",
|
|
23
|
+
layout=widgets.Layout(width="300px"),
|
|
24
|
+
)
|
|
25
|
+
self.load_btn = widgets.Button(description="Load", button_style="primary")
|
|
26
|
+
self.load_btn.on_click(self._load)
|
|
27
|
+
self.body = widgets.HTML(value="<i>Select a room to visualize.</i>")
|
|
28
|
+
controls = widgets.HBox(children=[self.room_select, self.load_btn])
|
|
29
|
+
super().__init__(children=[self.header, controls, self.body], **kwargs)
|
|
30
|
+
|
|
31
|
+
def _refresh_rooms(self):
|
|
32
|
+
names = list(self._client.rooms.keys())
|
|
33
|
+
if not names:
|
|
34
|
+
names = ["(no rooms)"]
|
|
35
|
+
self.room_select.options = names
|
|
36
|
+
|
|
37
|
+
def _load(self, btn):
|
|
38
|
+
self._refresh_rooms()
|
|
39
|
+
name = self.room_select.value
|
|
40
|
+
if name in self._client.rooms:
|
|
41
|
+
room = self._client.rooms[name]
|
|
42
|
+
self.body.value = render_room(room.to_dict())
|
|
43
|
+
else:
|
|
44
|
+
self.body.value = "<i>No room selected.</i>"
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IPython widgets for interactive agent control.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import ipywidgets as widgets
|
|
10
|
+
from IPython.display import display
|
|
11
|
+
|
|
12
|
+
from .kernel import OCClient, FleetAgent
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgentConfigWidget(widgets.VBox):
|
|
16
|
+
"""Dropdown configuration for modules and interface type."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, client: OCClient | None = None, **kwargs):
|
|
19
|
+
self._client = client or OCClient()
|
|
20
|
+
self.module_dropdown = widgets.Dropdown(
|
|
21
|
+
options=["vision", "text", "inference", "analysis"],
|
|
22
|
+
value="vision",
|
|
23
|
+
description="Module:",
|
|
24
|
+
)
|
|
25
|
+
self.interface_dropdown = widgets.Dropdown(
|
|
26
|
+
options=["magic", "api", "widget", "cli"],
|
|
27
|
+
value="magic",
|
|
28
|
+
description="Interface:",
|
|
29
|
+
)
|
|
30
|
+
self.apply_btn = widgets.Button(description="Apply", button_style="primary")
|
|
31
|
+
self.apply_btn.on_click(self._on_apply)
|
|
32
|
+
self.output = widgets.Output()
|
|
33
|
+
super().__init__(
|
|
34
|
+
children=[self.module_dropdown, self.interface_dropdown, self.apply_btn, self.output],
|
|
35
|
+
**kwargs,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def _on_apply(self, btn):
|
|
39
|
+
with self.output:
|
|
40
|
+
print(f"Configured: module={self.module_dropdown.value}, interface={self.interface_dropdown.value}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FleetStatusWidget(widgets.VBox):
|
|
44
|
+
"""Live fleet health dashboard."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, client: OCClient | None = None, **kwargs):
|
|
47
|
+
self._client = client or OCClient()
|
|
48
|
+
self.refresh_btn = widgets.Button(description="Refresh", button_style="info")
|
|
49
|
+
self.refresh_btn.on_click(self._refresh)
|
|
50
|
+
self.status_html = widgets.HTML(value="<i>No fleet data</i>")
|
|
51
|
+
super().__init__(children=[self.refresh_btn, self.status_html], **kwargs)
|
|
52
|
+
|
|
53
|
+
def _refresh(self, btn):
|
|
54
|
+
fleet = self._client.discover_fleet()
|
|
55
|
+
rows = "".join(
|
|
56
|
+
f"<tr><td>{a['name']}</td><td>{a['status']}</td><td>{', '.join(a['modules'])}</td></tr>"
|
|
57
|
+
for a in fleet
|
|
58
|
+
)
|
|
59
|
+
self.status_html.value = (
|
|
60
|
+
"<table><tr><th>Agent</th><th>Status</th><th>Modules</th></tr>"
|
|
61
|
+
f"{rows}</table>"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SenseShadowWidget(widgets.VBox):
|
|
66
|
+
"""Rendered sense output (HTML / ANSI)."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, **kwargs):
|
|
69
|
+
self.shadow_output = widgets.HTML(value="<i>No shadow captured</i>")
|
|
70
|
+
super().__init__(children=[self.shadow_output], **kwargs)
|
|
71
|
+
|
|
72
|
+
def show(self, html: str):
|
|
73
|
+
self.shadow_output.value = html
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class RoomGraphWidget(widgets.VBox):
|
|
77
|
+
"""Plato room knowledge graph visualization."""
|
|
78
|
+
|
|
79
|
+
def __init__(self, client: OCClient | None = None, **kwargs):
|
|
80
|
+
self._client = client or OCClient()
|
|
81
|
+
self.graph_output = widgets.HTML(value="<i>No room loaded</i>")
|
|
82
|
+
super().__init__(children=[self.graph_output], **kwargs)
|
|
83
|
+
|
|
84
|
+
def show_room(self, room: dict):
|
|
85
|
+
tiles = room.get("tiles", [])
|
|
86
|
+
nodes = "".join(
|
|
87
|
+
f'<div style="border:1px solid #4a9;padding:4px 8px;margin:2px;border-radius:4px;'
|
|
88
|
+
f'background:#1a2a1a;display:inline-block">{t["name"]} <small>(CR {t["cr_score"]})</small></div>'
|
|
89
|
+
for t in tiles
|
|
90
|
+
)
|
|
91
|
+
self.graph_output.value = (
|
|
92
|
+
f'<div style="font-family:monospace">'
|
|
93
|
+
f'<b>Room: {room.get("name","?")}</b><br>{nodes}</div>'
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TickBoardWidget(widgets.VBox):
|
|
98
|
+
"""Scrolling tick messages."""
|
|
99
|
+
|
|
100
|
+
def __init__(self, **kwargs):
|
|
101
|
+
self.tick_area = widgets.HTML(value="<i>No ticks</i>")
|
|
102
|
+
self._messages: list[str] = []
|
|
103
|
+
super().__init__(children=[self.tick_area], **kwargs)
|
|
104
|
+
|
|
105
|
+
def add_tick(self, message: str):
|
|
106
|
+
self._messages.append(message)
|
|
107
|
+
bubbles = "".join(
|
|
108
|
+
f'<div style="background:#2a3a5a;color:#ddd;padding:6px 10px;margin:3px 0;'
|
|
109
|
+
f'border-radius:8px;max-width:70%">{m}</div>'
|
|
110
|
+
for m in self._messages[-20:]
|
|
111
|
+
)
|
|
112
|
+
self.tick_area.value = bubbles
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class PolicyWidget(widgets.VBox):
|
|
116
|
+
"""Policy configuration with toggles."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, **kwargs):
|
|
119
|
+
self.auto_register = widgets.ToggleButton(value=True, description="Auto-register")
|
|
120
|
+
self.auto_capture = widgets.ToggleButton(value=True, description="Auto-capture")
|
|
121
|
+
self.verbose = widgets.ToggleButton(value=False, description="Verbose")
|
|
122
|
+
self.output = widgets.Output()
|
|
123
|
+
super().__init__(
|
|
124
|
+
children=[self.auto_register, self.auto_capture, self.verbose, self.output],
|
|
125
|
+
**kwargs,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def policy(self) -> dict:
|
|
130
|
+
return {
|
|
131
|
+
"auto_register": self.auto_register.value,
|
|
132
|
+
"auto_capture": self.auto_capture.value,
|
|
133
|
+
"verbose": self.verbose.value,
|
|
134
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openconstruct-jupyter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Jupyter notebook integration for OpenConstruct — connect, sense, and build with agents
|
|
5
|
+
Author: SuperInstance
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Framework :: Jupyter
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: ipython>=7.0
|
|
15
|
+
Requires-Dist: ipywidgets>=8.0
|
|
16
|
+
Requires-Dist: rich>=13.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
20
|
+
|
|
21
|
+
# openconstruct-jupyter — Jupyter Integration for OpenConstruct
|
|
22
|
+
|
|
23
|
+
Connect OpenConstruct to your Jupyter notebooks in one line. Cell magic, widgets, fleet panels, and DataFrame-to-room conversion.
|
|
24
|
+
|
|
25
|
+
**Part of [SuperInstance OpenConstruct](https://github.com/SuperInstance/OpenConstruct).**
|
|
26
|
+
|
|
27
|
+
## What This Gives You
|
|
28
|
+
|
|
29
|
+
- **Cell magic** — `%%openconstruct --sense vision` captures output as a sense shadow
|
|
30
|
+
- **DataFrame → Plato room** — turn any pandas DataFrame into a knowledge graph
|
|
31
|
+
- **Python function → agent tool** — `oc.from_function(greet, name="my_tool")`
|
|
32
|
+
- **Fleet panel** — inline widget showing discovered fleet nodes
|
|
33
|
+
- **Room viewer** — visualize Plato room tiles and dependencies
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# Load the extension
|
|
39
|
+
%load_ext openconstruct_jupyter
|
|
40
|
+
|
|
41
|
+
# Connect
|
|
42
|
+
import openconstruct_jupyter as oc
|
|
43
|
+
oc.connect(notebook=True)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Cell Magic
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
%%openconstruct --sense vision
|
|
50
|
+
# Cell output captured as a vision shadow
|
|
51
|
+
|
|
52
|
+
%%openconstruct --tick "analysis complete"
|
|
53
|
+
# Post a tick to the fleet
|
|
54
|
+
|
|
55
|
+
%%openconstruct --room my-analysis
|
|
56
|
+
# Create or load a Plato room
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Python API
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import pandas as pd
|
|
63
|
+
|
|
64
|
+
# DataFrame → room
|
|
65
|
+
df = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
|
|
66
|
+
oc.from_dataframe(df, name="my_data")
|
|
67
|
+
|
|
68
|
+
# Function → agent tool
|
|
69
|
+
def greet(name):
|
|
70
|
+
return f"Hello, {name}!"
|
|
71
|
+
oc.from_function(greet, name="greet_tool")
|
|
72
|
+
|
|
73
|
+
# REST API → agent resource
|
|
74
|
+
oc.from_api("https://api.example.com/data", name="external_data")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install openconstruct-jupyter
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Testing
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pytest tests/ -v
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
openconstruct_jupyter/__init__.py
|
|
4
|
+
openconstruct_jupyter/display.py
|
|
5
|
+
openconstruct_jupyter/fleet_panel.py
|
|
6
|
+
openconstruct_jupyter/kernel.py
|
|
7
|
+
openconstruct_jupyter/magic.py
|
|
8
|
+
openconstruct_jupyter/room_viewer.py
|
|
9
|
+
openconstruct_jupyter/widgets.py
|
|
10
|
+
openconstruct_jupyter.egg-info/PKG-INFO
|
|
11
|
+
openconstruct_jupyter.egg-info/SOURCES.txt
|
|
12
|
+
openconstruct_jupyter.egg-info/dependency_links.txt
|
|
13
|
+
openconstruct_jupyter.egg-info/entry_points.txt
|
|
14
|
+
openconstruct_jupyter.egg-info/requires.txt
|
|
15
|
+
openconstruct_jupyter.egg-info/top_level.txt
|
|
16
|
+
tests/test_magic.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openconstruct_jupyter
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openconstruct-jupyter"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Jupyter notebook integration for OpenConstruct — connect, sense, and build with agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "SuperInstance" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Framework :: Jupyter",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"ipython>=7.0",
|
|
22
|
+
"ipywidgets>=8.0",
|
|
23
|
+
"rich>=13.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = ["pytest>=7.0", "pytest-asyncio"]
|
|
28
|
+
|
|
29
|
+
[project.entry-points."IPython.kernel"]
|
|
30
|
+
openconstruct = "openconstruct_jupyter.magic:load_ipython_extension"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
include = ["openconstruct_jupyter*"]
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for openconstruct-jupyter.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ── Kernel / OCClient ──────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
class TestOCClient:
|
|
12
|
+
def setup_method(self):
|
|
13
|
+
from openconstruct_jupyter.kernel import OCClient
|
|
14
|
+
self.client = OCClient()
|
|
15
|
+
|
|
16
|
+
def test_connect(self):
|
|
17
|
+
result = self.client.connect()
|
|
18
|
+
assert result["status"] == "connected"
|
|
19
|
+
assert self.client.connected is True
|
|
20
|
+
|
|
21
|
+
def test_discover_fleet_empty(self):
|
|
22
|
+
fleet = self.client.discover_fleet()
|
|
23
|
+
assert fleet == []
|
|
24
|
+
|
|
25
|
+
def test_discover_fleet_after_connect(self):
|
|
26
|
+
self.client.connect()
|
|
27
|
+
fleet = self.client.discover_fleet()
|
|
28
|
+
assert len(fleet) == 1
|
|
29
|
+
assert fleet[0]["name"] == "notebook-agent"
|
|
30
|
+
|
|
31
|
+
def test_register_shadow(self):
|
|
32
|
+
shadow = self.client.register_shadow("vision", "hello world")
|
|
33
|
+
assert shadow.sense_type == "vision"
|
|
34
|
+
assert shadow.output == "hello world"
|
|
35
|
+
assert shadow.success is True
|
|
36
|
+
assert len(self.client.shadows) == 1
|
|
37
|
+
|
|
38
|
+
def test_register_shadow_failure(self):
|
|
39
|
+
shadow = self.client.register_shadow("text", "error", success=False)
|
|
40
|
+
assert shadow.success is False
|
|
41
|
+
|
|
42
|
+
def test_post_tick(self):
|
|
43
|
+
tick = self.client.post_tick("test tick")
|
|
44
|
+
assert tick.message == "test tick"
|
|
45
|
+
assert len(self.client.ticks) == 1
|
|
46
|
+
|
|
47
|
+
def test_load_room_creates_new(self):
|
|
48
|
+
room = self.client.load_room("test-room")
|
|
49
|
+
assert room.name == "test-room"
|
|
50
|
+
assert "test-room" in self.client.rooms
|
|
51
|
+
|
|
52
|
+
def test_load_room_with_context(self):
|
|
53
|
+
room = self.client.load_room("ctx-room", context="some context")
|
|
54
|
+
assert len(room.tiles) == 1
|
|
55
|
+
assert room.tiles[0].value == "some context"
|
|
56
|
+
|
|
57
|
+
def test_load_room_idempotent(self):
|
|
58
|
+
self.client.load_room("same-room")
|
|
59
|
+
self.client.load_room("same-room")
|
|
60
|
+
assert len(self.client.rooms) == 1
|
|
61
|
+
|
|
62
|
+
def test_publish_room(self):
|
|
63
|
+
result = self.client.publish_room()
|
|
64
|
+
assert "room_id" in result
|
|
65
|
+
|
|
66
|
+
def test_from_dataframe(self):
|
|
67
|
+
import pandas as pd
|
|
68
|
+
df = pd.DataFrame({"x": range(50), "y": range(50)})
|
|
69
|
+
tile = self.client.from_dataframe(df, name="test_df")
|
|
70
|
+
assert tile.name == "test_df"
|
|
71
|
+
assert tile.source == "dataframe"
|
|
72
|
+
assert tile.cr_score > 0
|
|
73
|
+
|
|
74
|
+
def test_from_function(self):
|
|
75
|
+
def my_func(x):
|
|
76
|
+
return x + 1
|
|
77
|
+
result = self.client.from_function(my_func, name="adder")
|
|
78
|
+
assert result["registered"] is True
|
|
79
|
+
assert "adder" in self.client.tools
|
|
80
|
+
|
|
81
|
+
def test_from_model(self):
|
|
82
|
+
class M:
|
|
83
|
+
pass
|
|
84
|
+
result = self.client.from_model(M(), name="test_model")
|
|
85
|
+
assert result["registered"] is True
|
|
86
|
+
|
|
87
|
+
def test_from_api(self):
|
|
88
|
+
result = self.client.from_api("https://example.com", name="ex_api")
|
|
89
|
+
assert result["type"] == "api"
|
|
90
|
+
assert "ex_api" in self.client.api_resources
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ── Display rendering ──────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
class TestDisplay:
|
|
96
|
+
def test_render_fleet_empty(self):
|
|
97
|
+
from openconstruct_jupyter.display import render_fleet
|
|
98
|
+
html = render_fleet([])
|
|
99
|
+
assert "No fleet agents" in html
|
|
100
|
+
|
|
101
|
+
def test_render_fleet_with_agents(self):
|
|
102
|
+
from openconstruct_jupyter.display import render_fleet
|
|
103
|
+
fleet = [{"agent_id": "a1", "name": "agent-1", "status": "online", "modules": ["vision"]}]
|
|
104
|
+
html = render_fleet(fleet)
|
|
105
|
+
assert "agent-1" in html
|
|
106
|
+
assert "online" in html
|
|
107
|
+
|
|
108
|
+
def test_render_room(self):
|
|
109
|
+
from openconstruct_jupyter.display import render_room
|
|
110
|
+
room = {"name": "test", "tiles": [{"name": "tile1", "cr_score": 0.5}]}
|
|
111
|
+
html = render_room(room)
|
|
112
|
+
assert "test" in html
|
|
113
|
+
assert "tile1" in html
|
|
114
|
+
|
|
115
|
+
def test_render_tick(self):
|
|
116
|
+
from openconstruct_jupyter.display import render_tick
|
|
117
|
+
html = render_tick("hello tick")
|
|
118
|
+
assert "hello tick" in html
|
|
119
|
+
assert "✅" in html
|
|
120
|
+
|
|
121
|
+
def test_render_shadow(self):
|
|
122
|
+
from openconstruct_jupyter.display import render_shadow
|
|
123
|
+
from openconstruct_jupyter.kernel import SenseShadow
|
|
124
|
+
shadow = SenseShadow(sense_type="vision", output="test output")
|
|
125
|
+
html = render_shadow(shadow)
|
|
126
|
+
assert "vision" in html
|
|
127
|
+
assert "test output" in html
|
|
128
|
+
|
|
129
|
+
def test_render_config_yaml(self):
|
|
130
|
+
from openconstruct_jupyter.display import render_config_yaml
|
|
131
|
+
html = render_config_yaml({"debug": True, "name": "test"})
|
|
132
|
+
assert "debug" in html
|
|
133
|
+
assert "test" in html
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ── Magic ──────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
class TestMagic:
|
|
139
|
+
def test_load_and_unload(self):
|
|
140
|
+
from openconstruct_jupyter.magic import load_ipython_extension, unload_ipython_extension
|
|
141
|
+
ipython = MagicMock()
|
|
142
|
+
load_ipython_extension(ipython)
|
|
143
|
+
ipython.register_magics.assert_called_once()
|
|
144
|
+
unload_ipython_extension(ipython) # should not raise
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ── Widgets ────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
class TestWidgets:
|
|
150
|
+
def test_agent_config_widget(self):
|
|
151
|
+
from openconstruct_jupyter.widgets import AgentConfigWidget
|
|
152
|
+
w = AgentConfigWidget()
|
|
153
|
+
assert w.module_dropdown.value == "vision"
|
|
154
|
+
|
|
155
|
+
def test_fleet_status_widget(self):
|
|
156
|
+
from openconstruct_jupyter.widgets import FleetStatusWidget
|
|
157
|
+
w = FleetStatusWidget()
|
|
158
|
+
assert "No fleet data" in w.status_html.value
|
|
159
|
+
|
|
160
|
+
def test_sense_shadow_widget(self):
|
|
161
|
+
from openconstruct_jupyter.widgets import SenseShadowWidget
|
|
162
|
+
w = SenseShadowWidget()
|
|
163
|
+
w.show("<b>test</b>")
|
|
164
|
+
assert "<b>test</b>" in w.shadow_output.value
|
|
165
|
+
|
|
166
|
+
def test_tick_board_widget(self):
|
|
167
|
+
from openconstruct_jupyter.widgets import TickBoardWidget
|
|
168
|
+
w = TickBoardWidget()
|
|
169
|
+
w.add_tick("hello")
|
|
170
|
+
assert "hello" in w.tick_area.value
|
|
171
|
+
|
|
172
|
+
def test_policy_widget(self):
|
|
173
|
+
from openconstruct_jupyter.widgets import PolicyWidget
|
|
174
|
+
w = PolicyWidget()
|
|
175
|
+
assert w.policy["auto_register"] is True
|
|
176
|
+
|
|
177
|
+
def test_room_graph_widget(self):
|
|
178
|
+
from openconstruct_jupyter.widgets import RoomGraphWidget
|
|
179
|
+
w = RoomGraphWidget()
|
|
180
|
+
w.show_room({"name": "test", "tiles": [{"name": "t1", "cr_score": 0.7}]})
|
|
181
|
+
assert "t1" in w.graph_output.value
|