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.
@@ -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,2 @@
1
+ [IPython.kernel]
2
+ openconstruct = openconstruct_jupyter.magic:load_ipython_extension
@@ -0,0 +1,7 @@
1
+ ipython>=7.0
2
+ ipywidgets>=8.0
3
+ rich>=13.0
4
+
5
+ [dev]
6
+ pytest>=7.0
7
+ pytest-asyncio
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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