openconstruct-jupyter 0.1.0__py3-none-any.whl
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/__init__.py +59 -0
- openconstruct_jupyter/display.py +86 -0
- openconstruct_jupyter/fleet_panel.py +38 -0
- openconstruct_jupyter/kernel.py +170 -0
- openconstruct_jupyter/magic.py +81 -0
- openconstruct_jupyter/room_viewer.py +44 -0
- openconstruct_jupyter/widgets.py +134 -0
- openconstruct_jupyter-0.1.0.dist-info/METADATA +91 -0
- openconstruct_jupyter-0.1.0.dist-info/RECORD +12 -0
- openconstruct_jupyter-0.1.0.dist-info/WHEEL +5 -0
- openconstruct_jupyter-0.1.0.dist-info/entry_points.txt +2 -0
- openconstruct_jupyter-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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,12 @@
|
|
|
1
|
+
openconstruct_jupyter/__init__.py,sha256=_BBAFb238o4O0-D17dWa-uxKozHbSHUUXrvQqF8X6Ac,1584
|
|
2
|
+
openconstruct_jupyter/display.py,sha256=G3SSgkpW4M6XFbznstuORwQ7UurWhg2Cr-e0J6PQNi0,3739
|
|
3
|
+
openconstruct_jupyter/fleet_panel.py,sha256=X8Q__3JHv9VKZCOjMwheFp39ZMKWTAFCQTt6xCfleRE,1399
|
|
4
|
+
openconstruct_jupyter/kernel.py,sha256=VW04NZcxpFFM4DyiW-tu9JKKZFPk04Vc1HeAaZO6GPA,5294
|
|
5
|
+
openconstruct_jupyter/magic.py,sha256=Ahfbu1CYHTD4ytq5itZBUa7j33h6FvjUVKQVbedfn58,2619
|
|
6
|
+
openconstruct_jupyter/room_viewer.py,sha256=MekoHLz2dtRZsIaKB-tysSLmvS2D8M4YPlWapili-0w,1497
|
|
7
|
+
openconstruct_jupyter/widgets.py,sha256=8oKOnLB2JlkuvnbU945P93jrr1V9S09wI4xJLgipZfM,4760
|
|
8
|
+
openconstruct_jupyter-0.1.0.dist-info/METADATA,sha256=D8eGOgxKFn55BWvlnGdKpL3fOCpDFT6xRZG7gTAy284,2307
|
|
9
|
+
openconstruct_jupyter-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
openconstruct_jupyter-0.1.0.dist-info/entry_points.txt,sha256=JYXTaurf4V1zHb5a6LZxp-MVqZyNcCnMVIUVMmnyATU,84
|
|
11
|
+
openconstruct_jupyter-0.1.0.dist-info/top_level.txt,sha256=Iq3ltnRV2khCRScenBXtYtJF483QTAI6-quo_MR4rgY,22
|
|
12
|
+
openconstruct_jupyter-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openconstruct_jupyter
|