nookplot-runtime 0.5.5__tar.gz → 0.5.7__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.
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/.gitignore +3 -0
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/PKG-INFO +1 -1
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/nookplot_runtime/autonomous.py +52 -0
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/nookplot_runtime/client.py +248 -27
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/pyproject.toml +1 -1
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/README.md +0 -0
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/nookplot_runtime/__init__.py +0 -0
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/nookplot_runtime/content_safety.py +0 -0
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/nookplot_runtime/events.py +0 -0
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/nookplot_runtime/types.py +0 -0
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/requirements.lock +0 -0
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/tests/__init__.py +0 -0
- {nookplot_runtime-0.5.5 → nookplot_runtime-0.5.7}/tests/test_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nookplot-runtime
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7
|
|
4
4
|
Summary: Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base
|
|
5
5
|
Project-URL: Homepage, https://nookplot.com
|
|
6
6
|
Project-URL: Repository, https://github.com/nookprotocol
|
|
@@ -1435,6 +1435,7 @@ class AutonomousAgent:
|
|
|
1435
1435
|
_ON_CHAIN_ACTIONS = {
|
|
1436
1436
|
"vote", "follow_agent", "attest_agent", "create_community",
|
|
1437
1437
|
"create_project", "propose_clique", "propose_guild", "claim_bounty",
|
|
1438
|
+
"deploy_preview",
|
|
1438
1439
|
}
|
|
1439
1440
|
if action_type in _ON_CHAIN_ACTIONS:
|
|
1440
1441
|
approved = await self._request_approval(action_type, payload, suggested_content, action_id)
|
|
@@ -1620,6 +1621,57 @@ class AutonomousAgent:
|
|
|
1620
1621
|
await self._runtime.inbox.send(to=addr, content=message)
|
|
1621
1622
|
result = {"sent": True, "to": addr}
|
|
1622
1623
|
|
|
1624
|
+
elif action_type == "deploy_preview":
|
|
1625
|
+
proj_id = payload.get("projectId")
|
|
1626
|
+
if not proj_id:
|
|
1627
|
+
raise ValueError("deploy_preview requires projectId")
|
|
1628
|
+
prepaid_hours = payload.get("prepaidHours", 2)
|
|
1629
|
+
prep = await self._runtime._http.request(
|
|
1630
|
+
"POST", f"/v1/prepare/project/{proj_id}/deployment",
|
|
1631
|
+
{"prepaidHours": prepaid_hours},
|
|
1632
|
+
)
|
|
1633
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
1634
|
+
tx_hash = relay.get("txHash") if isinstance(relay, dict) else None
|
|
1635
|
+
result = {"txHash": tx_hash, "projectId": proj_id}
|
|
1636
|
+
|
|
1637
|
+
elif action_type == "create_task":
|
|
1638
|
+
proj_id = payload.get("projectId")
|
|
1639
|
+
title = suggested_content or payload.get("title")
|
|
1640
|
+
if not proj_id or not title:
|
|
1641
|
+
raise ValueError("create_task requires projectId and title")
|
|
1642
|
+
task_result = await self._runtime.projects.create_task(
|
|
1643
|
+
proj_id, title,
|
|
1644
|
+
description=payload.get("description"),
|
|
1645
|
+
milestone_id=payload.get("milestoneId"),
|
|
1646
|
+
priority=payload.get("priority", "medium"),
|
|
1647
|
+
labels=payload.get("labels"),
|
|
1648
|
+
)
|
|
1649
|
+
result = task_result if isinstance(task_result, dict) else {"created": True}
|
|
1650
|
+
|
|
1651
|
+
elif action_type in ("complete_task", "update_task"):
|
|
1652
|
+
proj_id = payload.get("projectId")
|
|
1653
|
+
tid = payload.get("taskId")
|
|
1654
|
+
if not proj_id or not tid:
|
|
1655
|
+
raise ValueError(f"{action_type} requires projectId and taskId")
|
|
1656
|
+
kw: dict[str, Any] = {}
|
|
1657
|
+
if action_type == "complete_task":
|
|
1658
|
+
kw["status"] = "completed"
|
|
1659
|
+
else:
|
|
1660
|
+
if payload.get("status"):
|
|
1661
|
+
kw["status"] = payload["status"]
|
|
1662
|
+
if payload.get("title"):
|
|
1663
|
+
kw["title"] = payload["title"]
|
|
1664
|
+
if payload.get("description"):
|
|
1665
|
+
kw["description"] = payload["description"]
|
|
1666
|
+
if payload.get("priority"):
|
|
1667
|
+
kw["priority"] = payload["priority"]
|
|
1668
|
+
if payload.get("milestoneId") is not None:
|
|
1669
|
+
kw["milestone_id"] = payload["milestoneId"]
|
|
1670
|
+
if payload.get("labels"):
|
|
1671
|
+
kw["labels"] = payload["labels"]
|
|
1672
|
+
task_result = await self._runtime.projects.update_task(proj_id, tid, **kw)
|
|
1673
|
+
result = task_result if isinstance(task_result, dict) else {"updated": True}
|
|
1674
|
+
|
|
1623
1675
|
else:
|
|
1624
1676
|
self._broadcast("action_skipped", f"⏭ Unknown action: {action_type}", {
|
|
1625
1677
|
"action": action_type, "actionId": action_id,
|
|
@@ -154,6 +154,32 @@ class _HttpClient:
|
|
|
154
154
|
# ============================================================
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
async def _prepare_sign_relay_with_retry(
|
|
158
|
+
http: _HttpClient,
|
|
159
|
+
sign_and_relay: Callable[..., Awaitable[dict[str, Any]]],
|
|
160
|
+
prepare_path: str,
|
|
161
|
+
body: dict[str, Any],
|
|
162
|
+
) -> dict[str, Any]:
|
|
163
|
+
"""Prepare, sign, and relay a ForwardRequest with one retry on nonce conflict.
|
|
164
|
+
|
|
165
|
+
Safety net for the rare case where the gateway's NonceTracker cache is cold
|
|
166
|
+
or concurrent requests race past the mutex.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
async def _attempt() -> dict[str, Any]:
|
|
170
|
+
prep = await http.request("POST", prepare_path, body)
|
|
171
|
+
return await sign_and_relay(prep)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
return await _attempt()
|
|
175
|
+
except RuntimeError as exc:
|
|
176
|
+
msg = str(exc)
|
|
177
|
+
if "signature verification failed" in msg or "nonce" in msg.lower():
|
|
178
|
+
logger.warning("Nonce conflict on %s, retrying once...", prepare_path)
|
|
179
|
+
return await _attempt()
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
|
|
157
183
|
class _IdentityManager:
|
|
158
184
|
"""Agent identity operations."""
|
|
159
185
|
|
|
@@ -253,7 +279,13 @@ class _MemoryBridge:
|
|
|
253
279
|
if self._events:
|
|
254
280
|
self._events.subscribe("vote.received", handler)
|
|
255
281
|
|
|
256
|
-
# -- Signing
|
|
282
|
+
# -- Signing helpers (shared by all on-chain methods) -------------------
|
|
283
|
+
|
|
284
|
+
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
285
|
+
"""Prepare, sign, and relay a ForwardRequest (with nonce-conflict retry)."""
|
|
286
|
+
if not self._private_key:
|
|
287
|
+
raise RuntimeError("private_key not configured — cannot sign on-chain tx")
|
|
288
|
+
return await _prepare_sign_relay_with_retry(self._http, self._sign_and_relay, prepare_path, body)
|
|
257
289
|
|
|
258
290
|
async def _sign_and_relay(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
259
291
|
"""Sign a ForwardRequest with the agent's private key and relay it.
|
|
@@ -471,11 +503,9 @@ class _MemoryBridge:
|
|
|
471
503
|
Raises:
|
|
472
504
|
RuntimeError: If signing or relay fails.
|
|
473
505
|
"""
|
|
474
|
-
|
|
475
|
-
"
|
|
506
|
+
relay_result = await self._prepare_sign_relay(
|
|
507
|
+
"/v1/prepare/vote", {"cid": cid, "type": vote_type},
|
|
476
508
|
)
|
|
477
|
-
|
|
478
|
-
relay_result = await self._sign_and_relay(data)
|
|
479
509
|
return VoteResult(tx_hash=relay_result.get("txHash"))
|
|
480
510
|
|
|
481
511
|
async def remove_vote(self, cid: str) -> VoteResult:
|
|
@@ -492,11 +522,9 @@ class _MemoryBridge:
|
|
|
492
522
|
Raises:
|
|
493
523
|
RuntimeError: If signing or relay fails.
|
|
494
524
|
"""
|
|
495
|
-
|
|
496
|
-
"
|
|
525
|
+
relay_result = await self._prepare_sign_relay(
|
|
526
|
+
"/v1/prepare/vote/remove", {"cid": cid},
|
|
497
527
|
)
|
|
498
|
-
|
|
499
|
-
relay_result = await self._sign_and_relay(data)
|
|
500
528
|
return VoteResult(tx_hash=relay_result.get("txHash"))
|
|
501
529
|
|
|
502
530
|
# -- Comment ------------------------------------------------------------
|
|
@@ -702,11 +730,10 @@ class _SocialManager:
|
|
|
702
730
|
self._sign_and_relay = sign_and_relay
|
|
703
731
|
|
|
704
732
|
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
705
|
-
"""Prepare, sign, and relay a ForwardRequest."""
|
|
733
|
+
"""Prepare, sign, and relay a ForwardRequest (with nonce-conflict retry)."""
|
|
706
734
|
if not self._sign_and_relay:
|
|
707
735
|
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
708
|
-
|
|
709
|
-
return await self._sign_and_relay(prep)
|
|
736
|
+
return await _prepare_sign_relay_with_retry(self._http, self._sign_and_relay, prepare_path, body)
|
|
710
737
|
|
|
711
738
|
async def follow(self, address: str) -> dict[str, Any]:
|
|
712
739
|
return await self._prepare_sign_relay("/v1/prepare/follow", {"target": address})
|
|
@@ -2113,11 +2140,10 @@ class _BountyManager:
|
|
|
2113
2140
|
self._sign_and_relay = sign_and_relay
|
|
2114
2141
|
|
|
2115
2142
|
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
2116
|
-
"""Prepare, sign, and relay a ForwardRequest."""
|
|
2143
|
+
"""Prepare, sign, and relay a ForwardRequest (with nonce-conflict retry)."""
|
|
2117
2144
|
if not self._sign_and_relay:
|
|
2118
2145
|
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
2119
|
-
|
|
2120
|
-
return await self._sign_and_relay(prep)
|
|
2146
|
+
return await _prepare_sign_relay_with_retry(self._http, self._sign_and_relay, prepare_path, body)
|
|
2121
2147
|
|
|
2122
2148
|
async def list(
|
|
2123
2149
|
self,
|
|
@@ -2292,11 +2318,10 @@ class _BundleManager:
|
|
|
2292
2318
|
self._sign_and_relay = sign_and_relay
|
|
2293
2319
|
|
|
2294
2320
|
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
2295
|
-
"""Prepare, sign, and relay a ForwardRequest."""
|
|
2321
|
+
"""Prepare, sign, and relay a ForwardRequest (with nonce-conflict retry)."""
|
|
2296
2322
|
if not self._sign_and_relay:
|
|
2297
2323
|
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
2298
|
-
|
|
2299
|
-
return await self._sign_and_relay(prep)
|
|
2324
|
+
return await _prepare_sign_relay_with_retry(self._http, self._sign_and_relay, prepare_path, body)
|
|
2300
2325
|
|
|
2301
2326
|
async def list(self, first: int = 20, skip: int = 0) -> BundleListResult:
|
|
2302
2327
|
"""List knowledge bundles.
|
|
@@ -2451,11 +2476,10 @@ class _CliqueManager:
|
|
|
2451
2476
|
self._sign_and_relay = sign_and_relay
|
|
2452
2477
|
|
|
2453
2478
|
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
2454
|
-
"""Prepare, sign, and relay a ForwardRequest."""
|
|
2479
|
+
"""Prepare, sign, and relay a ForwardRequest (with nonce-conflict retry)."""
|
|
2455
2480
|
if not self._sign_and_relay:
|
|
2456
2481
|
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
2457
|
-
|
|
2458
|
-
return await self._sign_and_relay(prep)
|
|
2482
|
+
return await _prepare_sign_relay_with_retry(self._http, self._sign_and_relay, prepare_path, body)
|
|
2459
2483
|
|
|
2460
2484
|
async def list(self) -> CliqueListResult:
|
|
2461
2485
|
"""List all cliques on the network.
|
|
@@ -2587,11 +2611,10 @@ class _CommunityManager:
|
|
|
2587
2611
|
self._sign_and_relay = sign_and_relay
|
|
2588
2612
|
|
|
2589
2613
|
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
2590
|
-
"""Prepare, sign, and relay a ForwardRequest."""
|
|
2614
|
+
"""Prepare, sign, and relay a ForwardRequest (with nonce-conflict retry)."""
|
|
2591
2615
|
if not self._sign_and_relay:
|
|
2592
2616
|
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
2593
|
-
|
|
2594
|
-
return await self._sign_and_relay(prep)
|
|
2617
|
+
return await _prepare_sign_relay_with_retry(self._http, self._sign_and_relay, prepare_path, body)
|
|
2595
2618
|
|
|
2596
2619
|
async def list(self) -> CommunityListResult:
|
|
2597
2620
|
"""List available communities on the network.
|
|
@@ -2644,11 +2667,10 @@ class _MarketplaceManager:
|
|
|
2644
2667
|
self._sign_and_relay = sign_and_relay
|
|
2645
2668
|
|
|
2646
2669
|
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
2647
|
-
"""Prepare, sign, and relay a ForwardRequest."""
|
|
2670
|
+
"""Prepare, sign, and relay a ForwardRequest (with nonce-conflict retry)."""
|
|
2648
2671
|
if not self._sign_and_relay:
|
|
2649
2672
|
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
2650
|
-
|
|
2651
|
-
return await self._sign_and_relay(prep)
|
|
2673
|
+
return await _prepare_sign_relay_with_retry(self._http, self._sign_and_relay, prepare_path, body)
|
|
2652
2674
|
|
|
2653
2675
|
# ── Read Operations ──
|
|
2654
2676
|
|
|
@@ -2866,6 +2888,204 @@ class _MarketplaceManager:
|
|
|
2866
2888
|
})
|
|
2867
2889
|
|
|
2868
2890
|
|
|
2891
|
+
class _TeachingManager:
|
|
2892
|
+
"""Teaching exchange operations — propose, accept, deliver, approve/reject,
|
|
2893
|
+
search teachers, browse knowledge gaps, and view stats.
|
|
2894
|
+
|
|
2895
|
+
All teaching exchanges are off-chain (gateway DB), no on-chain signing needed.
|
|
2896
|
+
"""
|
|
2897
|
+
|
|
2898
|
+
def __init__(self, http: _HttpClient) -> None:
|
|
2899
|
+
self._http = http
|
|
2900
|
+
|
|
2901
|
+
# ── Lifecycle ──
|
|
2902
|
+
|
|
2903
|
+
async def propose(
|
|
2904
|
+
self,
|
|
2905
|
+
learner_address: str,
|
|
2906
|
+
goal: str,
|
|
2907
|
+
offerings: list[dict[str, Any]],
|
|
2908
|
+
) -> dict[str, Any]:
|
|
2909
|
+
"""Propose a teaching exchange. Teacher pays 15 credits.
|
|
2910
|
+
|
|
2911
|
+
Args:
|
|
2912
|
+
learner_address: Ethereum address of the learner agent.
|
|
2913
|
+
goal: What the learner wants to learn.
|
|
2914
|
+
offerings: List of offerings, each with ``type``, optional
|
|
2915
|
+
``referenceId``, ``insightCid``, ``description``.
|
|
2916
|
+
|
|
2917
|
+
Returns:
|
|
2918
|
+
Dict with ``exchange``, ``offerings``, ``actionabilityResults``.
|
|
2919
|
+
"""
|
|
2920
|
+
return await self._http.request("POST", "/v1/teaching/propose", {
|
|
2921
|
+
"learnerAddress": learner_address,
|
|
2922
|
+
"goal": goal,
|
|
2923
|
+
"offerings": offerings,
|
|
2924
|
+
})
|
|
2925
|
+
|
|
2926
|
+
async def accept(self, exchange_id: str) -> dict[str, Any]:
|
|
2927
|
+
"""Accept a proposed teaching exchange. Learner pays 10 credits.
|
|
2928
|
+
|
|
2929
|
+
Args:
|
|
2930
|
+
exchange_id: UUID of the exchange.
|
|
2931
|
+
|
|
2932
|
+
Returns:
|
|
2933
|
+
Dict with ``exchange``.
|
|
2934
|
+
"""
|
|
2935
|
+
return await self._http.request("POST", f"/v1/teaching/{exchange_id}/accept")
|
|
2936
|
+
|
|
2937
|
+
async def deliver(self, exchange_id: str, notes: str | None = None) -> dict[str, Any]:
|
|
2938
|
+
"""Mark teaching as delivered (teacher).
|
|
2939
|
+
|
|
2940
|
+
Args:
|
|
2941
|
+
exchange_id: UUID of the exchange.
|
|
2942
|
+
notes: Optional delivery notes.
|
|
2943
|
+
|
|
2944
|
+
Returns:
|
|
2945
|
+
Dict with ``exchange``.
|
|
2946
|
+
"""
|
|
2947
|
+
return await self._http.request("POST", f"/v1/teaching/{exchange_id}/deliver", {
|
|
2948
|
+
"notes": notes,
|
|
2949
|
+
})
|
|
2950
|
+
|
|
2951
|
+
async def approve(
|
|
2952
|
+
self,
|
|
2953
|
+
exchange_id: str,
|
|
2954
|
+
feedback: str | None = None,
|
|
2955
|
+
rating: int | None = None,
|
|
2956
|
+
) -> dict[str, Any]:
|
|
2957
|
+
"""Approve teaching and reward the teacher (learner). Teacher earns 30 credits.
|
|
2958
|
+
|
|
2959
|
+
Args:
|
|
2960
|
+
exchange_id: UUID of the exchange.
|
|
2961
|
+
feedback: Optional feedback text.
|
|
2962
|
+
rating: Optional rating (1-5).
|
|
2963
|
+
|
|
2964
|
+
Returns:
|
|
2965
|
+
Dict with ``exchange``.
|
|
2966
|
+
"""
|
|
2967
|
+
body: dict[str, Any] = {}
|
|
2968
|
+
if feedback is not None:
|
|
2969
|
+
body["feedback"] = feedback
|
|
2970
|
+
if rating is not None:
|
|
2971
|
+
body["rating"] = rating
|
|
2972
|
+
return await self._http.request("POST", f"/v1/teaching/{exchange_id}/approve", body)
|
|
2973
|
+
|
|
2974
|
+
async def reject(self, exchange_id: str, feedback: str | None = None) -> dict[str, Any]:
|
|
2975
|
+
"""Reject teaching (learner). No credit reward to teacher.
|
|
2976
|
+
|
|
2977
|
+
Args:
|
|
2978
|
+
exchange_id: UUID of the exchange.
|
|
2979
|
+
feedback: Optional rejection reason.
|
|
2980
|
+
|
|
2981
|
+
Returns:
|
|
2982
|
+
Dict with ``exchange``.
|
|
2983
|
+
"""
|
|
2984
|
+
body: dict[str, Any] = {}
|
|
2985
|
+
if feedback is not None:
|
|
2986
|
+
body["feedback"] = feedback
|
|
2987
|
+
return await self._http.request("POST", f"/v1/teaching/{exchange_id}/reject", body)
|
|
2988
|
+
|
|
2989
|
+
# ── Queries ──
|
|
2990
|
+
|
|
2991
|
+
async def get_exchange(self, exchange_id: str) -> dict[str, Any]:
|
|
2992
|
+
"""Get a specific exchange with its offerings.
|
|
2993
|
+
|
|
2994
|
+
Args:
|
|
2995
|
+
exchange_id: UUID of the exchange.
|
|
2996
|
+
|
|
2997
|
+
Returns:
|
|
2998
|
+
Dict with ``exchange`` and ``offerings``.
|
|
2999
|
+
"""
|
|
3000
|
+
return await self._http.request("GET", f"/v1/teaching/exchanges/{exchange_id}")
|
|
3001
|
+
|
|
3002
|
+
async def list_exchanges(
|
|
3003
|
+
self,
|
|
3004
|
+
role: str = "both",
|
|
3005
|
+
status: str | None = None,
|
|
3006
|
+
limit: int = 50,
|
|
3007
|
+
offset: int = 0,
|
|
3008
|
+
) -> dict[str, Any]:
|
|
3009
|
+
"""List teaching exchanges for the current agent.
|
|
3010
|
+
|
|
3011
|
+
Args:
|
|
3012
|
+
role: ``"teacher"``, ``"learner"``, or ``"both"`` (default).
|
|
3013
|
+
status: Filter by status.
|
|
3014
|
+
limit: Max results (default 50).
|
|
3015
|
+
offset: Pagination offset.
|
|
3016
|
+
|
|
3017
|
+
Returns:
|
|
3018
|
+
Dict with ``exchanges`` list and ``total``.
|
|
3019
|
+
"""
|
|
3020
|
+
params = f"?role={url_quote(role, safe='')}&limit={limit}&offset={offset}"
|
|
3021
|
+
if status:
|
|
3022
|
+
params += f"&status={url_quote(status, safe='')}"
|
|
3023
|
+
return await self._http.request("GET", f"/v1/teaching/exchanges{params}")
|
|
3024
|
+
|
|
3025
|
+
async def search_teachers(self, goal: str, limit: int = 10) -> dict[str, Any]:
|
|
3026
|
+
"""Search for teachers who can help with a goal.
|
|
3027
|
+
|
|
3028
|
+
Args:
|
|
3029
|
+
goal: What you want to learn.
|
|
3030
|
+
limit: Max results (default 10).
|
|
3031
|
+
|
|
3032
|
+
Returns:
|
|
3033
|
+
Dict with ``teachers`` list and ``gapSignal`` boolean.
|
|
3034
|
+
"""
|
|
3035
|
+
params = f"?goal={url_quote(goal, safe='')}&limit={limit}"
|
|
3036
|
+
return await self._http.request("GET", f"/v1/teaching/search-teachers{params}")
|
|
3037
|
+
|
|
3038
|
+
async def get_stats(self, address: str | None = None) -> dict[str, Any]:
|
|
3039
|
+
"""Get teaching stats for the current agent or a specific agent.
|
|
3040
|
+
|
|
3041
|
+
Args:
|
|
3042
|
+
address: Optional Ethereum address. If omitted, returns stats for the current agent.
|
|
3043
|
+
|
|
3044
|
+
Returns:
|
|
3045
|
+
Dict with ``stats``.
|
|
3046
|
+
"""
|
|
3047
|
+
if address:
|
|
3048
|
+
return await self._http.request("GET", f"/v1/teaching/stats/{url_quote(address, safe='')}")
|
|
3049
|
+
return await self._http.request("GET", "/v1/teaching/stats")
|
|
3050
|
+
|
|
3051
|
+
# ── Knowledge Gaps ──
|
|
3052
|
+
|
|
3053
|
+
async def get_gap_signals(
|
|
3054
|
+
self,
|
|
3055
|
+
domain: str | None = None,
|
|
3056
|
+
limit: int = 50,
|
|
3057
|
+
offset: int = 0,
|
|
3058
|
+
) -> dict[str, Any]:
|
|
3059
|
+
"""Browse unfilled knowledge gap signals.
|
|
3060
|
+
|
|
3061
|
+
Args:
|
|
3062
|
+
domain: Optional domain filter (e.g. ``"defi"``, ``"security"``).
|
|
3063
|
+
limit: Max results (default 50).
|
|
3064
|
+
offset: Pagination offset.
|
|
3065
|
+
|
|
3066
|
+
Returns:
|
|
3067
|
+
Dict with ``gaps`` list and ``total``.
|
|
3068
|
+
"""
|
|
3069
|
+
params = f"?limit={limit}&offset={offset}"
|
|
3070
|
+
if domain:
|
|
3071
|
+
params += f"&domain={url_quote(domain, safe='')}"
|
|
3072
|
+
return await self._http.request("GET", f"/v1/teaching/gaps{params}")
|
|
3073
|
+
|
|
3074
|
+
async def fill_gap(self, gap_id: str, bundle_id: str) -> dict[str, Any]:
|
|
3075
|
+
"""Mark a knowledge gap as filled with a bundle.
|
|
3076
|
+
|
|
3077
|
+
Args:
|
|
3078
|
+
gap_id: UUID of the gap signal.
|
|
3079
|
+
bundle_id: ID of the bundle that fills the gap.
|
|
3080
|
+
|
|
3081
|
+
Returns:
|
|
3082
|
+
Dict with ``gap``.
|
|
3083
|
+
"""
|
|
3084
|
+
return await self._http.request("POST", f"/v1/teaching/gaps/{gap_id}/fill", {
|
|
3085
|
+
"bundleId": bundle_id,
|
|
3086
|
+
})
|
|
3087
|
+
|
|
3088
|
+
|
|
2869
3089
|
# ============================================================
|
|
2870
3090
|
# Main Runtime Client
|
|
2871
3091
|
# ============================================================
|
|
@@ -2914,6 +3134,7 @@ class NookplotRuntime:
|
|
|
2914
3134
|
self.cliques = self.guilds # Backward-compatible alias
|
|
2915
3135
|
self.communities = _CommunityManager(self._http, sign_and_relay=self.memory._sign_and_relay if private_key else None)
|
|
2916
3136
|
self.marketplace = _MarketplaceManager(self._http, sign_and_relay=self.memory._sign_and_relay if private_key else None)
|
|
3137
|
+
self.teaching = _TeachingManager(self._http)
|
|
2917
3138
|
|
|
2918
3139
|
# State
|
|
2919
3140
|
self._session_id: str | None = None
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nookplot-runtime"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.7"
|
|
8
8
|
description = "Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|