nookplot-runtime 0.5.21__tar.gz → 0.5.23__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.21 → nookplot_runtime-0.5.23}/PKG-INFO +1 -1
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/nookplot_runtime/autonomous.py +217 -7
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/nookplot_runtime/client.py +78 -0
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/pyproject.toml +1 -1
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/.gitignore +0 -0
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/README.md +0 -0
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/nookplot_runtime/__init__.py +0 -0
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/nookplot_runtime/content_safety.py +0 -0
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/nookplot_runtime/events.py +0 -0
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/nookplot_runtime/types.py +0 -0
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/requirements.lock +0 -0
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/tests/__init__.py +0 -0
- {nookplot_runtime-0.5.21 → nookplot_runtime-0.5.23}/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.23
|
|
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
|
|
@@ -283,6 +283,10 @@ class AutonomousAgent:
|
|
|
283
283
|
return f"revision_requested:{data.get('agreementId', '')}"
|
|
284
284
|
if signal_type == "review_received":
|
|
285
285
|
return f"review_received:{data.get('agreementId', '')}"
|
|
286
|
+
# Webhook signals
|
|
287
|
+
if signal_type in ("webhook_received", "webhook.received"):
|
|
288
|
+
source = data.get("source", "")
|
|
289
|
+
return f"webhook:{source}:{int(time.time())}"
|
|
286
290
|
return f"{signal_type}:{addr}:{data.get('channelId', '')}:{data.get('postCid', '')}"
|
|
287
291
|
|
|
288
292
|
async def _handle_signal(self, data: dict[str, Any]) -> None:
|
|
@@ -436,6 +440,9 @@ class AutonomousAgent:
|
|
|
436
440
|
self._broadcast("action_skipped", f"Received {data.get('rating', '?')}-star review on Agreement #{data.get('agreementId', '?')}", {
|
|
437
441
|
"signalType": signal_type, "agreementId": data.get("agreementId"), "rating": data.get("rating"),
|
|
438
442
|
})
|
|
443
|
+
# ── Webhook signals ──
|
|
444
|
+
elif signal_type in ("webhook_received", "webhook.received"):
|
|
445
|
+
await self._handle_webhook_received(data)
|
|
439
446
|
else:
|
|
440
447
|
self._broadcast("action_skipped", f"⏭ Unhandled signal type: {signal_type}", {
|
|
441
448
|
"signalType": signal_type,
|
|
@@ -1111,6 +1118,46 @@ class AutonomousAgent:
|
|
|
1111
1118
|
"action": "revision_requested", "agreementId": agreement_id, "error": str(exc),
|
|
1112
1119
|
})
|
|
1113
1120
|
|
|
1121
|
+
# ── Webhook signal handler ──
|
|
1122
|
+
|
|
1123
|
+
async def _handle_webhook_received(self, data: dict[str, Any]) -> None:
|
|
1124
|
+
"""External webhook pushed an event — decide how to react."""
|
|
1125
|
+
source = data.get("source", "unknown")
|
|
1126
|
+
webhook_payload = data.get("payload", data.get("body", {}))
|
|
1127
|
+
webhook_headers = data.get("headers", {})
|
|
1128
|
+
|
|
1129
|
+
try:
|
|
1130
|
+
payload_preview = str(webhook_payload)[:500] if webhook_payload else "(empty)"
|
|
1131
|
+
|
|
1132
|
+
prompt = (
|
|
1133
|
+
f"{UNTRUSTED_CONTENT_INSTRUCTION}\n\n"
|
|
1134
|
+
"You received a webhook event from an external service.\n"
|
|
1135
|
+
f"Source: {source}\n"
|
|
1136
|
+
f"Payload:\n{wrap_untrusted(payload_preview, 'webhook payload')}\n\n"
|
|
1137
|
+
"Decide what to do with this event. You can:\n"
|
|
1138
|
+
"- egress_request: Make an outbound HTTP request (params: url, method, headers, body)\n"
|
|
1139
|
+
"- execute_tool: Run a registered tool (params: toolName, args)\n"
|
|
1140
|
+
"- send_message: Notify another agent (params: to, content)\n"
|
|
1141
|
+
"- post: Share an update (params: body, community)\n"
|
|
1142
|
+
"- ignore: Take no action\n\n"
|
|
1143
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>"
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1146
|
+
assert self._generate_response is not None
|
|
1147
|
+
response = await self._generate_response(prompt)
|
|
1148
|
+
text = (response or "").strip()
|
|
1149
|
+
|
|
1150
|
+
if text and not text.upper().startswith("IGNORE"):
|
|
1151
|
+
await self._parse_and_execute_action(text)
|
|
1152
|
+
else:
|
|
1153
|
+
self._broadcast("action_skipped", f"⏭ Webhook from {source} — no action taken", {
|
|
1154
|
+
"signalType": "webhook_received", "source": source,
|
|
1155
|
+
})
|
|
1156
|
+
except Exception as exc:
|
|
1157
|
+
self._broadcast("error", f"Webhook handling failed: {exc}", {
|
|
1158
|
+
"action": "webhook_received", "source": source, "error": str(exc),
|
|
1159
|
+
})
|
|
1160
|
+
|
|
1114
1161
|
# ── Bounty application/submission signal handlers ──
|
|
1115
1162
|
|
|
1116
1163
|
async def _handle_bounty_application_submitted(self, data: dict[str, Any]) -> None:
|
|
@@ -1966,7 +2013,7 @@ class AutonomousAgent:
|
|
|
1966
2013
|
tx_hash = pub.get("txHash") if isinstance(pub, dict) else getattr(pub, "tx_hash", None)
|
|
1967
2014
|
result = {"cid": pub.get("cid") if isinstance(pub, dict) else getattr(pub, "cid", None), "txHash": tx_hash}
|
|
1968
2015
|
|
|
1969
|
-
elif action_type
|
|
2016
|
+
elif action_type in ("create_post", "publish"):
|
|
1970
2017
|
community = payload.get("community", "general")
|
|
1971
2018
|
title = payload.get("title") or (suggested_content[:100] if suggested_content else "Untitled")
|
|
1972
2019
|
body = suggested_content or payload.get("body", "")
|
|
@@ -2113,7 +2160,7 @@ class AutonomousAgent:
|
|
|
2113
2160
|
}
|
|
2114
2161
|
)
|
|
2115
2162
|
|
|
2116
|
-
elif action_type
|
|
2163
|
+
elif action_type in ("gateway_commit", "commit_files"):
|
|
2117
2164
|
pid = payload.get("projectId")
|
|
2118
2165
|
files = payload.get("files")
|
|
2119
2166
|
msg = suggested_content or payload.get("message", "Autonomous commit")
|
|
@@ -2353,7 +2400,7 @@ class AutonomousAgent:
|
|
|
2353
2400
|
task_result = await self._runtime.projects.update_task(proj_id, tid, **kw)
|
|
2354
2401
|
result = task_result if isinstance(task_result, dict) else {"updated": True}
|
|
2355
2402
|
|
|
2356
|
-
elif action_type
|
|
2403
|
+
elif action_type in ("find_matching_agents", "find_agents"):
|
|
2357
2404
|
skills = payload.get("skills", [])
|
|
2358
2405
|
if not skills:
|
|
2359
2406
|
raise ValueError("find_matching_agents requires skills array")
|
|
@@ -2580,7 +2627,7 @@ class AutonomousAgent:
|
|
|
2580
2627
|
elif action_type == "create_listing":
|
|
2581
2628
|
# Alias for list_service
|
|
2582
2629
|
prep = await self._runtime._http.request("POST", "/v1/prepare/service/list", payload)
|
|
2583
|
-
relay = await self._sign_and_relay(prep)
|
|
2630
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2584
2631
|
tx_hash = relay.get("txHash", "")
|
|
2585
2632
|
result = relay
|
|
2586
2633
|
|
|
@@ -2589,7 +2636,7 @@ class AutonomousAgent:
|
|
|
2589
2636
|
if not gid:
|
|
2590
2637
|
raise ValueError("approve_guild requires guildId")
|
|
2591
2638
|
prep = await self._runtime._http.request("POST", f"/v1/prepare/guild/{gid}/approve", {})
|
|
2592
|
-
relay = await self._sign_and_relay(prep)
|
|
2639
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2593
2640
|
tx_hash = relay.get("txHash", "")
|
|
2594
2641
|
result = relay
|
|
2595
2642
|
|
|
@@ -2598,7 +2645,7 @@ class AutonomousAgent:
|
|
|
2598
2645
|
if not gid:
|
|
2599
2646
|
raise ValueError("reject_guild requires guildId")
|
|
2600
2647
|
prep = await self._runtime._http.request("POST", f"/v1/prepare/guild/{gid}/reject", {})
|
|
2601
|
-
relay = await self._sign_and_relay(prep)
|
|
2648
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2602
2649
|
tx_hash = relay.get("txHash", "")
|
|
2603
2650
|
result = relay
|
|
2604
2651
|
|
|
@@ -2607,10 +2654,173 @@ class AutonomousAgent:
|
|
|
2607
2654
|
if not gid:
|
|
2608
2655
|
raise ValueError("leave_guild requires guildId")
|
|
2609
2656
|
prep = await self._runtime._http.request("POST", f"/v1/prepare/guild/{gid}/leave", {})
|
|
2610
|
-
relay = await self._sign_and_relay(prep)
|
|
2657
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2611
2658
|
tx_hash = relay.get("txHash", "")
|
|
2612
2659
|
result = relay
|
|
2613
2660
|
|
|
2661
|
+
# ── Bounty access grant/deny ───────────────────────
|
|
2662
|
+
elif action_type == "grant":
|
|
2663
|
+
proj_id = payload.get("projectId")
|
|
2664
|
+
bounty_id = payload.get("bountyId")
|
|
2665
|
+
req_id = payload.get("requestId")
|
|
2666
|
+
if not proj_id or not bounty_id or not req_id:
|
|
2667
|
+
raise ValueError("grant requires projectId, bountyId, requestId")
|
|
2668
|
+
result = await self._runtime._http.request(
|
|
2669
|
+
"POST", f"/v1/projects/{proj_id}/bounties/{bounty_id}/grant-access",
|
|
2670
|
+
{"requestId": req_id},
|
|
2671
|
+
)
|
|
2672
|
+
|
|
2673
|
+
elif action_type == "deny":
|
|
2674
|
+
proj_id = payload.get("projectId")
|
|
2675
|
+
bounty_id = payload.get("bountyId")
|
|
2676
|
+
req_id = payload.get("requestId")
|
|
2677
|
+
if not proj_id or not bounty_id or not req_id:
|
|
2678
|
+
raise ValueError("deny requires projectId, bountyId, requestId")
|
|
2679
|
+
result = await self._runtime._http.request(
|
|
2680
|
+
"POST", f"/v1/projects/{proj_id}/bounties/{bounty_id}/deny-access",
|
|
2681
|
+
{"requestId": req_id},
|
|
2682
|
+
)
|
|
2683
|
+
|
|
2684
|
+
# ── Team invitations ──────────────────────────────
|
|
2685
|
+
elif action_type == "accept_invitation":
|
|
2686
|
+
inv_id = payload.get("invitationId")
|
|
2687
|
+
if not inv_id:
|
|
2688
|
+
raise ValueError("accept_invitation requires invitationId")
|
|
2689
|
+
result = await self._runtime._http.request(
|
|
2690
|
+
"POST", f"/v1/teams/invitations/{inv_id}/accept", {},
|
|
2691
|
+
)
|
|
2692
|
+
|
|
2693
|
+
elif action_type == "decline_invitation":
|
|
2694
|
+
inv_id = payload.get("invitationId")
|
|
2695
|
+
if not inv_id:
|
|
2696
|
+
raise ValueError("decline_invitation requires invitationId")
|
|
2697
|
+
result = await self._runtime._http.request(
|
|
2698
|
+
"POST", f"/v1/teams/invitations/{inv_id}/decline", {},
|
|
2699
|
+
)
|
|
2700
|
+
|
|
2701
|
+
# ── Service update ────────────────────────────────
|
|
2702
|
+
elif action_type == "update_service":
|
|
2703
|
+
listing_id = payload.get("listingId")
|
|
2704
|
+
if not listing_id:
|
|
2705
|
+
raise ValueError("update_service requires listingId")
|
|
2706
|
+
prep = await self._runtime._http.request(
|
|
2707
|
+
"POST", "/v1/prepare/service/update", payload,
|
|
2708
|
+
)
|
|
2709
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2710
|
+
tx_hash = relay.get("txHash", "")
|
|
2711
|
+
result = relay
|
|
2712
|
+
|
|
2713
|
+
# ── Additional bounty lifecycle ───────────────────
|
|
2714
|
+
elif action_type == "approve_bounty_work":
|
|
2715
|
+
bounty_id = payload.get("bountyId")
|
|
2716
|
+
if not bounty_id:
|
|
2717
|
+
raise ValueError("approve_bounty_work requires bountyId")
|
|
2718
|
+
prep = await self._runtime._http.request(
|
|
2719
|
+
"POST", f"/v1/prepare/bounty/{bounty_id}/approve", {},
|
|
2720
|
+
)
|
|
2721
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2722
|
+
tx_hash = relay.get("txHash", "")
|
|
2723
|
+
result = relay
|
|
2724
|
+
|
|
2725
|
+
elif action_type == "dispute_bounty_work":
|
|
2726
|
+
bounty_id = payload.get("bountyId")
|
|
2727
|
+
if not bounty_id:
|
|
2728
|
+
raise ValueError("dispute_bounty_work requires bountyId")
|
|
2729
|
+
prep = await self._runtime._http.request(
|
|
2730
|
+
"POST", f"/v1/prepare/bounty/{bounty_id}/dispute", {},
|
|
2731
|
+
)
|
|
2732
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2733
|
+
tx_hash = relay.get("txHash", "")
|
|
2734
|
+
result = relay
|
|
2735
|
+
|
|
2736
|
+
elif action_type == "cancel_bounty":
|
|
2737
|
+
bounty_id = payload.get("bountyId")
|
|
2738
|
+
if not bounty_id:
|
|
2739
|
+
raise ValueError("cancel_bounty requires bountyId")
|
|
2740
|
+
prep = await self._runtime._http.request(
|
|
2741
|
+
"POST", f"/v1/prepare/bounty/{bounty_id}/cancel", {},
|
|
2742
|
+
)
|
|
2743
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2744
|
+
tx_hash = relay.get("txHash", "")
|
|
2745
|
+
result = relay
|
|
2746
|
+
|
|
2747
|
+
elif action_type == "unclaim_bounty":
|
|
2748
|
+
bounty_id = payload.get("bountyId")
|
|
2749
|
+
if not bounty_id:
|
|
2750
|
+
raise ValueError("unclaim_bounty requires bountyId")
|
|
2751
|
+
prep = await self._runtime._http.request(
|
|
2752
|
+
"POST", f"/v1/prepare/bounty/{bounty_id}/unclaim", {},
|
|
2753
|
+
)
|
|
2754
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2755
|
+
tx_hash = relay.get("txHash", "")
|
|
2756
|
+
result = relay
|
|
2757
|
+
|
|
2758
|
+
# ── Real World Actions ──────────────────────────────
|
|
2759
|
+
elif action_type in ("egress_request", "http_request"):
|
|
2760
|
+
# Outbound HTTP request via the egress proxy (0.15 credits)
|
|
2761
|
+
target_url = payload.get("url", "")
|
|
2762
|
+
http_method = (payload.get("method", "GET")).upper()
|
|
2763
|
+
if not target_url:
|
|
2764
|
+
raise ValueError("egress_request requires url")
|
|
2765
|
+
http_result = await self._runtime.tools.http_request(
|
|
2766
|
+
url=target_url,
|
|
2767
|
+
method=http_method,
|
|
2768
|
+
headers=payload.get("headers"),
|
|
2769
|
+
body=payload.get("body"),
|
|
2770
|
+
timeout=payload.get("timeout"),
|
|
2771
|
+
credential_service=payload.get("credentialService"),
|
|
2772
|
+
)
|
|
2773
|
+
result = http_result if isinstance(http_result, dict) else {"response": http_result}
|
|
2774
|
+
|
|
2775
|
+
elif action_type == "execute_tool":
|
|
2776
|
+
# Execute a registered tool through the action registry
|
|
2777
|
+
tool_name = payload.get("toolName") or payload.get("name", "")
|
|
2778
|
+
tool_args = payload.get("args") or payload.get("input", {})
|
|
2779
|
+
if not tool_name:
|
|
2780
|
+
raise ValueError("execute_tool requires toolName")
|
|
2781
|
+
tool_result = await self._runtime.tools.execute_tool(tool_name, tool_args)
|
|
2782
|
+
result = tool_result if isinstance(tool_result, dict) else {"output": tool_result}
|
|
2783
|
+
|
|
2784
|
+
elif action_type == "connect_mcp_server":
|
|
2785
|
+
# Connect to an external MCP server
|
|
2786
|
+
server_url = payload.get("serverUrl", "")
|
|
2787
|
+
server_name = payload.get("serverName") or payload.get("name", "")
|
|
2788
|
+
if not server_url or not server_name:
|
|
2789
|
+
raise ValueError("connect_mcp_server requires serverUrl and serverName")
|
|
2790
|
+
mcp_result = await self._runtime.tools.connect_mcp_server(server_url, server_name)
|
|
2791
|
+
result = mcp_result if isinstance(mcp_result, dict) else {"connected": True}
|
|
2792
|
+
|
|
2793
|
+
elif action_type == "disconnect_mcp_server":
|
|
2794
|
+
server_id = payload.get("serverId", "")
|
|
2795
|
+
if not server_id:
|
|
2796
|
+
raise ValueError("disconnect_mcp_server requires serverId")
|
|
2797
|
+
await self._runtime.tools.disconnect_mcp_server(server_id)
|
|
2798
|
+
result = {"disconnected": True, "serverId": server_id}
|
|
2799
|
+
|
|
2800
|
+
elif action_type in ("call_mcp_tool", "use_mcp_tool"):
|
|
2801
|
+
# Call a tool from a connected MCP server — goes through action registry
|
|
2802
|
+
mcp_tool_name = payload.get("toolName") or payload.get("name", "")
|
|
2803
|
+
mcp_tool_args = payload.get("args") or payload.get("input", {})
|
|
2804
|
+
if not mcp_tool_name:
|
|
2805
|
+
raise ValueError("call_mcp_tool requires toolName")
|
|
2806
|
+
mcp_tool_result = await self._runtime.tools.execute_tool(mcp_tool_name, mcp_tool_args)
|
|
2807
|
+
result = mcp_tool_result if isinstance(mcp_tool_result, dict) else {"output": mcp_tool_result}
|
|
2808
|
+
|
|
2809
|
+
elif action_type == "register_webhook":
|
|
2810
|
+
# Register a webhook source for inbound events
|
|
2811
|
+
source = payload.get("source") or payload.get("name", "")
|
|
2812
|
+
if not source:
|
|
2813
|
+
raise ValueError("register_webhook requires source")
|
|
2814
|
+
config: dict[str, Any] = {}
|
|
2815
|
+
if payload.get("secret"):
|
|
2816
|
+
config["secret"] = payload["secret"]
|
|
2817
|
+
if payload.get("signatureHeader"):
|
|
2818
|
+
config["signatureHeader"] = payload["signatureHeader"]
|
|
2819
|
+
if payload.get("eventMapping"):
|
|
2820
|
+
config["eventMapping"] = payload["eventMapping"]
|
|
2821
|
+
reg_result = await self._runtime.tools.register_webhook(source, config or None)
|
|
2822
|
+
result = reg_result if isinstance(reg_result, dict) else {"registered": True}
|
|
2823
|
+
|
|
2614
2824
|
else:
|
|
2615
2825
|
self._broadcast("action_skipped", f"⏭ Unknown action: {action_type}", {
|
|
2616
2826
|
"action": action_type, "actionId": action_id,
|
|
@@ -1943,6 +1943,84 @@ class _ToolManager:
|
|
|
1943
1943
|
data = await self._http.request("GET", "/v1/agents/me/mcp/tools")
|
|
1944
1944
|
return data.get("data", [])
|
|
1945
1945
|
|
|
1946
|
+
# ── Webhook Management ──
|
|
1947
|
+
|
|
1948
|
+
async def register_webhook(
|
|
1949
|
+
self,
|
|
1950
|
+
source: str,
|
|
1951
|
+
config: dict[str, Any] | None = None,
|
|
1952
|
+
) -> dict[str, Any]:
|
|
1953
|
+
"""Register a webhook source so external services can push events."""
|
|
1954
|
+
data = await self._http.request(
|
|
1955
|
+
"POST",
|
|
1956
|
+
"/v1/agents/me/webhooks",
|
|
1957
|
+
{"source": source, "config": config or {}},
|
|
1958
|
+
)
|
|
1959
|
+
return data.get("data", {})
|
|
1960
|
+
|
|
1961
|
+
async def list_webhooks(self) -> list[dict[str, Any]]:
|
|
1962
|
+
"""List registered webhook sources."""
|
|
1963
|
+
data = await self._http.request("GET", "/v1/agents/me/webhooks")
|
|
1964
|
+
return data.get("data", [])
|
|
1965
|
+
|
|
1966
|
+
async def remove_webhook(self, source: str) -> None:
|
|
1967
|
+
"""Remove a webhook registration."""
|
|
1968
|
+
await self._http.request(
|
|
1969
|
+
"DELETE", f"/v1/agents/me/webhooks/{url_quote(source)}"
|
|
1970
|
+
)
|
|
1971
|
+
|
|
1972
|
+
async def get_webhook_log(self, page: int = 0) -> list[dict[str, Any]]:
|
|
1973
|
+
"""Get webhook event log (recent deliveries)."""
|
|
1974
|
+
data = await self._http.request(
|
|
1975
|
+
"GET", f"/v1/agents/me/webhooks/log?page={page}"
|
|
1976
|
+
)
|
|
1977
|
+
return data.get("data", [])
|
|
1978
|
+
|
|
1979
|
+
# ── Egress Allowlist Management ──
|
|
1980
|
+
|
|
1981
|
+
async def get_egress_allowlist(self) -> list[dict[str, Any]]:
|
|
1982
|
+
"""Get the egress allowlist (domains this agent is allowed to call)."""
|
|
1983
|
+
data = await self._http.request("GET", "/v1/agents/me/egress")
|
|
1984
|
+
return data.get("allowlist", [])
|
|
1985
|
+
|
|
1986
|
+
async def add_egress_domain(
|
|
1987
|
+
self,
|
|
1988
|
+
domain: str,
|
|
1989
|
+
max_requests_per_hour: int | None = None,
|
|
1990
|
+
) -> dict[str, Any]:
|
|
1991
|
+
"""Add or update a domain on the egress allowlist."""
|
|
1992
|
+
payload: dict[str, Any] = {"domain": domain}
|
|
1993
|
+
if max_requests_per_hour is not None:
|
|
1994
|
+
payload["maxRequestsPerHour"] = max_requests_per_hour
|
|
1995
|
+
return await self._http.request("PUT", "/v1/agents/me/egress", payload)
|
|
1996
|
+
|
|
1997
|
+
async def remove_egress_domain(self, domain: str) -> None:
|
|
1998
|
+
"""Remove a domain from the egress allowlist."""
|
|
1999
|
+
await self._http.request(
|
|
2000
|
+
"PUT", "/v1/agents/me/egress", {"domain": domain, "remove": True}
|
|
2001
|
+
)
|
|
2002
|
+
|
|
2003
|
+
# ── Credential Management ──
|
|
2004
|
+
|
|
2005
|
+
async def store_credential(self, service: str, api_key: str) -> None:
|
|
2006
|
+
"""Store an encrypted API credential for use with egress requests."""
|
|
2007
|
+
await self._http.request(
|
|
2008
|
+
"POST",
|
|
2009
|
+
"/v1/agents/me/credentials",
|
|
2010
|
+
{"service": service, "apiKey": api_key},
|
|
2011
|
+
)
|
|
2012
|
+
|
|
2013
|
+
async def list_credentials(self) -> list[dict[str, Any]]:
|
|
2014
|
+
"""List stored credential services (names only — keys are never exposed)."""
|
|
2015
|
+
data = await self._http.request("GET", "/v1/agents/me/credentials")
|
|
2016
|
+
return data.get("credentials", [])
|
|
2017
|
+
|
|
2018
|
+
async def remove_credential(self, service: str) -> None:
|
|
2019
|
+
"""Remove a stored credential."""
|
|
2020
|
+
await self._http.request(
|
|
2021
|
+
"DELETE", f"/v1/agents/me/credentials/{url_quote(service)}"
|
|
2022
|
+
)
|
|
2023
|
+
|
|
1946
2024
|
|
|
1947
2025
|
# ============================================================
|
|
1948
2026
|
# Proactive Manager
|
|
@@ -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.23"
|
|
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
|
|
File without changes
|