nookplot-runtime 0.5.22__tar.gz → 0.5.24__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.22 → nookplot_runtime-0.5.24}/PKG-INFO +1 -1
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/nookplot_runtime/autonomous.py +363 -7
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/nookplot_runtime/client.py +59 -0
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/pyproject.toml +1 -1
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/.gitignore +0 -0
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/README.md +0 -0
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/nookplot_runtime/__init__.py +0 -0
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/nookplot_runtime/content_safety.py +0 -0
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/nookplot_runtime/events.py +0 -0
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/nookplot_runtime/types.py +0 -0
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/requirements.lock +0 -0
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/tests/__init__.py +0 -0
- {nookplot_runtime-0.5.22 → nookplot_runtime-0.5.24}/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.24
|
|
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
|
|
@@ -268,6 +268,33 @@ class AutonomousAgent:
|
|
|
268
268
|
return f"bounty_cancelled:{data.get('bountyId', '')}"
|
|
269
269
|
if signal_type == "bounty_claimer_approved":
|
|
270
270
|
return f"bounty_claimer_approved:{data.get('bountyId', '')}:{addr}"
|
|
271
|
+
# Project collaboration signals
|
|
272
|
+
if signal_type == "task_created":
|
|
273
|
+
return f"task_new:{data.get('taskId', '')}"
|
|
274
|
+
if signal_type == "task_assigned":
|
|
275
|
+
return f"task_assign:{data.get('taskId', '')}:{addr}"
|
|
276
|
+
if signal_type == "task_completed":
|
|
277
|
+
return f"task_done:{data.get('taskId', '')}"
|
|
278
|
+
if signal_type == "milestone_reached":
|
|
279
|
+
return f"milestone:{data.get('milestoneId', '')}"
|
|
280
|
+
if signal_type == "agent_mentioned":
|
|
281
|
+
return f"mention:{data.get('broadcastId', '')}:{addr}"
|
|
282
|
+
if signal_type == "project_status_update":
|
|
283
|
+
return f"broadcast:{data.get('broadcastId', '')}"
|
|
284
|
+
if signal_type == "review_comment_added":
|
|
285
|
+
return f"rev_comment:{data.get('commitId', '')}:{addr}"
|
|
286
|
+
if signal_type == "bounty_posted_to_project":
|
|
287
|
+
return f"proj_bounty:{data.get('bountyId', '')}"
|
|
288
|
+
if signal_type == "bounty_access_requested":
|
|
289
|
+
return f"bounty_req:{data.get('requestId', '')}"
|
|
290
|
+
if signal_type == "bounty_access_granted":
|
|
291
|
+
return f"bounty_grant:{data.get('requestId', '')}"
|
|
292
|
+
if signal_type == "bounty_access_denied":
|
|
293
|
+
return f"bounty_deny:{data.get('requestId', '')}"
|
|
294
|
+
if signal_type == "project_bounty_claimed":
|
|
295
|
+
return f"proj_bounty_claim:{data.get('bountyId', '')}"
|
|
296
|
+
if signal_type == "project_bounty_completed":
|
|
297
|
+
return f"proj_bounty_done:{data.get('bountyId', '')}"
|
|
271
298
|
# Marketplace signals
|
|
272
299
|
if signal_type == "agreement_created":
|
|
273
300
|
return f"agreement_created:{data.get('agreementId', '')}"
|
|
@@ -421,6 +448,53 @@ class AutonomousAgent:
|
|
|
421
448
|
})
|
|
422
449
|
elif signal_type == "bounty_claimer_approved":
|
|
423
450
|
await self._handle_bounty_claimer_approved(data)
|
|
451
|
+
# ── Project collaboration signals ──
|
|
452
|
+
elif signal_type == "task_created":
|
|
453
|
+
await self._handle_task_created(data)
|
|
454
|
+
elif signal_type == "task_assigned":
|
|
455
|
+
self._broadcast("action_skipped", f"📋 Task assigned to you in project #{data.get('projectId', '?')}", {
|
|
456
|
+
"signalType": signal_type, "projectId": data.get("projectId"), "taskId": data.get("taskId"),
|
|
457
|
+
})
|
|
458
|
+
elif signal_type == "task_completed":
|
|
459
|
+
self._broadcast("action_skipped", f"✅ Task completed in project #{data.get('projectId', '?')}", {
|
|
460
|
+
"signalType": signal_type, "projectId": data.get("projectId"), "taskId": data.get("taskId"),
|
|
461
|
+
})
|
|
462
|
+
elif signal_type == "milestone_reached":
|
|
463
|
+
self._broadcast("action_skipped", f"🏆 Milestone reached in project #{data.get('projectId', '?')}", {
|
|
464
|
+
"signalType": signal_type, "projectId": data.get("projectId"),
|
|
465
|
+
})
|
|
466
|
+
elif signal_type == "agent_mentioned":
|
|
467
|
+
await self._handle_agent_mentioned(data)
|
|
468
|
+
elif signal_type == "project_status_update":
|
|
469
|
+
self._broadcast("action_skipped", f"📢 Project status update for #{data.get('projectId', '?')}", {
|
|
470
|
+
"signalType": signal_type, "projectId": data.get("projectId"),
|
|
471
|
+
})
|
|
472
|
+
elif signal_type == "review_comment_added":
|
|
473
|
+
await self._handle_review_comment_added(data)
|
|
474
|
+
elif signal_type == "bounty_posted_to_project":
|
|
475
|
+
await self._handle_bounty_posted_to_project(data)
|
|
476
|
+
elif signal_type == "bounty_access_requested":
|
|
477
|
+
await self._handle_bounty_access_requested(data)
|
|
478
|
+
elif signal_type == "bounty_access_granted":
|
|
479
|
+
self._broadcast("action_skipped", f"🔓 Bounty access granted for bounty #{data.get('bountyId', '?')}", {
|
|
480
|
+
"signalType": signal_type, "bountyId": data.get("bountyId"),
|
|
481
|
+
})
|
|
482
|
+
elif signal_type == "bounty_access_denied":
|
|
483
|
+
self._broadcast("action_skipped", f"🔒 Bounty access denied for bounty #{data.get('bountyId', '?')}", {
|
|
484
|
+
"signalType": signal_type, "bountyId": data.get("bountyId"),
|
|
485
|
+
})
|
|
486
|
+
elif signal_type == "project_bounty_claimed":
|
|
487
|
+
self._broadcast("action_skipped", f"Bounty #{data.get('bountyId', '?')} claimed in your project", {
|
|
488
|
+
"signalType": signal_type, "bountyId": data.get("bountyId"),
|
|
489
|
+
})
|
|
490
|
+
elif signal_type == "project_bounty_completed":
|
|
491
|
+
self._broadcast("action_skipped", f"Bounty #{data.get('bountyId', '?')} completed in your project", {
|
|
492
|
+
"signalType": signal_type, "bountyId": data.get("bountyId"),
|
|
493
|
+
})
|
|
494
|
+
elif signal_type in ("task_deleted", "status_updated"):
|
|
495
|
+
self._broadcast("action_skipped", f"📋 {signal_type} in project (noted)", {
|
|
496
|
+
"signalType": signal_type,
|
|
497
|
+
})
|
|
424
498
|
# ── Marketplace signals ──
|
|
425
499
|
elif signal_type == "agreement_created":
|
|
426
500
|
await self._handle_agreement_created(data)
|
|
@@ -1952,6 +2026,191 @@ class AutonomousAgent:
|
|
|
1952
2026
|
"action": "pending_review", "projectId": project_id, "error": str(exc),
|
|
1953
2027
|
})
|
|
1954
2028
|
|
|
2029
|
+
# ================================================================
|
|
2030
|
+
# Project signal handlers (task_created, agent_mentioned, etc.)
|
|
2031
|
+
# ================================================================
|
|
2032
|
+
|
|
2033
|
+
async def _handle_task_created(self, data: dict[str, Any]) -> None:
|
|
2034
|
+
"""Handle a new task created in a project — just log, don't auto-respond (too noisy)."""
|
|
2035
|
+
project_id = data.get("projectId", "")
|
|
2036
|
+
task_id = data.get("taskId", "")
|
|
2037
|
+
title = data.get("title", "")
|
|
2038
|
+
if self._verbose:
|
|
2039
|
+
logger.info("[autonomous] New task created: %s (%s) in project %s", title, task_id, project_id)
|
|
2040
|
+
|
|
2041
|
+
async def _handle_agent_mentioned(self, data: dict[str, Any]) -> None:
|
|
2042
|
+
"""Handle being @mentioned in a project broadcast — reply in project channel."""
|
|
2043
|
+
project_id = data.get("projectId", "")
|
|
2044
|
+
broadcast_id = data.get("broadcastId", "")
|
|
2045
|
+
sender = data.get("senderAddress", "")
|
|
2046
|
+
preview = data.get("messagePreview", "")
|
|
2047
|
+
if not project_id or not preview:
|
|
2048
|
+
return
|
|
2049
|
+
|
|
2050
|
+
try:
|
|
2051
|
+
assert self._generate_response is not None
|
|
2052
|
+
safe_preview = sanitize_for_prompt(preview)
|
|
2053
|
+
prompt = (
|
|
2054
|
+
f"{UNTRUSTED_CONTENT_INSTRUCTION}\n\n"
|
|
2055
|
+
"You were @mentioned in a project broadcast on Nookplot.\n"
|
|
2056
|
+
f"Project: {project_id}\n"
|
|
2057
|
+
f"From: {sender[:12]}...\n"
|
|
2058
|
+
f"Message:\n{wrap_untrusted(safe_preview, 'broadcast mention')}\n\n"
|
|
2059
|
+
"Write a reply to the mention in the project channel.\n"
|
|
2060
|
+
"If nothing to say, respond with: [SKIP]\n\n"
|
|
2061
|
+
"Your reply (under 400 chars):"
|
|
2062
|
+
)
|
|
2063
|
+
|
|
2064
|
+
response = await self._generate_response(prompt)
|
|
2065
|
+
content = (response or "").strip()
|
|
2066
|
+
if content and content != "[SKIP]":
|
|
2067
|
+
try:
|
|
2068
|
+
await self._runtime.channels.send_to_project(project_id, content)
|
|
2069
|
+
self._broadcast("action_executed", f"💬 Replied to mention in broadcast {broadcast_id}", {
|
|
2070
|
+
"action": "agent_mentioned", "projectId": project_id, "broadcastId": broadcast_id,
|
|
2071
|
+
})
|
|
2072
|
+
except Exception:
|
|
2073
|
+
pass
|
|
2074
|
+
|
|
2075
|
+
except Exception as exc:
|
|
2076
|
+
self._broadcast("error", f"✗ Agent mentioned handling failed: {exc}", {
|
|
2077
|
+
"action": "agent_mentioned", "projectId": project_id, "error": str(exc),
|
|
2078
|
+
})
|
|
2079
|
+
|
|
2080
|
+
async def _handle_review_comment_added(self, data: dict[str, Any]) -> None:
|
|
2081
|
+
"""Handle a review comment on a commit — respond in project channel."""
|
|
2082
|
+
project_id = data.get("projectId", "")
|
|
2083
|
+
commit_id = data.get("commitId", "")
|
|
2084
|
+
reviewer = data.get("senderAddress", "")
|
|
2085
|
+
preview = data.get("messagePreview", "")
|
|
2086
|
+
if not preview or not project_id:
|
|
2087
|
+
return
|
|
2088
|
+
|
|
2089
|
+
try:
|
|
2090
|
+
assert self._generate_response is not None
|
|
2091
|
+
safe_preview = sanitize_for_prompt(preview)
|
|
2092
|
+
prompt = (
|
|
2093
|
+
f"{UNTRUSTED_CONTENT_INSTRUCTION}\n\n"
|
|
2094
|
+
"Someone left a review comment on your commit.\n"
|
|
2095
|
+
f"Reviewer: {reviewer[:12]}...\n"
|
|
2096
|
+
f"Comment:\n{wrap_untrusted(safe_preview, 'review comment')}\n\n"
|
|
2097
|
+
"Write a brief response for the project channel.\n"
|
|
2098
|
+
"If there's nothing meaningful to say, respond with: [SKIP]\n\n"
|
|
2099
|
+
"Your response (under 300 chars):"
|
|
2100
|
+
)
|
|
2101
|
+
|
|
2102
|
+
response = await self._generate_response(prompt)
|
|
2103
|
+
content = (response or "").strip()
|
|
2104
|
+
if content and content != "[SKIP]":
|
|
2105
|
+
try:
|
|
2106
|
+
await self._runtime.channels.send_to_project(project_id, content)
|
|
2107
|
+
self._broadcast("action_executed", f"💬 Responded to review comment on {commit_id[:8]}", {
|
|
2108
|
+
"action": "review_comment_response", "projectId": project_id, "commitId": commit_id,
|
|
2109
|
+
})
|
|
2110
|
+
except Exception:
|
|
2111
|
+
pass
|
|
2112
|
+
|
|
2113
|
+
except Exception as exc:
|
|
2114
|
+
self._broadcast("error", f"✗ Review comment handling failed: {exc}", {
|
|
2115
|
+
"action": "review_comment_added", "projectId": project_id, "error": str(exc),
|
|
2116
|
+
})
|
|
2117
|
+
|
|
2118
|
+
async def _handle_bounty_posted_to_project(self, data: dict[str, Any]) -> None:
|
|
2119
|
+
"""Handle a bounty posted to a project — express interest in project channel."""
|
|
2120
|
+
project_id = data.get("projectId", "")
|
|
2121
|
+
bounty_id = data.get("bountyId", "")
|
|
2122
|
+
preview = data.get("messagePreview", "")
|
|
2123
|
+
if not project_id:
|
|
2124
|
+
return
|
|
2125
|
+
|
|
2126
|
+
if self._verbose:
|
|
2127
|
+
logger.info("[autonomous] Bounty posted to project: %s", bounty_id)
|
|
2128
|
+
|
|
2129
|
+
try:
|
|
2130
|
+
assert self._generate_response is not None
|
|
2131
|
+
safe_preview = sanitize_for_prompt(preview)
|
|
2132
|
+
prompt = (
|
|
2133
|
+
f"{UNTRUSTED_CONTENT_INSTRUCTION}\n\n"
|
|
2134
|
+
"A bounty was linked to a project you collaborate on.\n"
|
|
2135
|
+
f"Project: {project_id}\n"
|
|
2136
|
+
f"Bounty ID: {bounty_id}\n"
|
|
2137
|
+
f"Details:\n{wrap_untrusted(safe_preview, 'bounty details')}\n\n"
|
|
2138
|
+
"Should you express interest? Write a brief message for the project channel.\n"
|
|
2139
|
+
"If not interested, respond with: [SKIP]\n\n"
|
|
2140
|
+
"Your response (under 300 chars):"
|
|
2141
|
+
)
|
|
2142
|
+
|
|
2143
|
+
response = await self._generate_response(prompt)
|
|
2144
|
+
content = (response or "").strip()
|
|
2145
|
+
if content and content != "[SKIP]":
|
|
2146
|
+
try:
|
|
2147
|
+
await self._runtime.channels.send_to_project(project_id, content)
|
|
2148
|
+
self._broadcast("action_executed", f"💬 Expressed interest in bounty {bounty_id}", {
|
|
2149
|
+
"action": "bounty_interest", "projectId": project_id, "bountyId": bounty_id,
|
|
2150
|
+
})
|
|
2151
|
+
except Exception:
|
|
2152
|
+
pass
|
|
2153
|
+
|
|
2154
|
+
except Exception as exc:
|
|
2155
|
+
self._broadcast("error", f"✗ Project bounty posted handling failed: {exc}", {
|
|
2156
|
+
"action": "bounty_posted_to_project", "projectId": project_id, "error": str(exc),
|
|
2157
|
+
})
|
|
2158
|
+
|
|
2159
|
+
async def _handle_bounty_access_requested(self, data: dict[str, Any]) -> None:
|
|
2160
|
+
"""Handle a bounty access request — decide whether to grant or deny."""
|
|
2161
|
+
project_id = data.get("projectId", "")
|
|
2162
|
+
request_id = data.get("requestId", "")
|
|
2163
|
+
bounty_id = data.get("bountyId", "")
|
|
2164
|
+
requester = data.get("senderAddress", "")
|
|
2165
|
+
preview = data.get("messagePreview", "")
|
|
2166
|
+
if not project_id or not bounty_id:
|
|
2167
|
+
return
|
|
2168
|
+
|
|
2169
|
+
if self._verbose:
|
|
2170
|
+
logger.info("[autonomous] Bounty access requested by %s for %s", requester[:10], bounty_id)
|
|
2171
|
+
|
|
2172
|
+
try:
|
|
2173
|
+
assert self._generate_response is not None
|
|
2174
|
+
safe_preview = sanitize_for_prompt(preview)
|
|
2175
|
+
prompt = (
|
|
2176
|
+
f"{UNTRUSTED_CONTENT_INSTRUCTION}\n\n"
|
|
2177
|
+
"An agent requested access to a bounty in your project.\n"
|
|
2178
|
+
f"Project: {project_id}\n"
|
|
2179
|
+
f"Bounty: {bounty_id}\n"
|
|
2180
|
+
f"Requester: {requester[:12]}...\n"
|
|
2181
|
+
f"Message:\n{wrap_untrusted(safe_preview, 'access request')}\n\n"
|
|
2182
|
+
"Decide: GRANT or DENY access.\n"
|
|
2183
|
+
"If you need more information, ask in the project channel.\n\n"
|
|
2184
|
+
"Format:\nDECISION: GRANT or DENY\nMESSAGE: brief response"
|
|
2185
|
+
)
|
|
2186
|
+
|
|
2187
|
+
response = await self._generate_response(prompt)
|
|
2188
|
+
text = (response or "").strip()
|
|
2189
|
+
|
|
2190
|
+
if "GRANT" in text.upper():
|
|
2191
|
+
try:
|
|
2192
|
+
await self._runtime._http.request(
|
|
2193
|
+
"POST",
|
|
2194
|
+
f"/v1/projects/{project_id}/bounties/{bounty_id}/grant-access",
|
|
2195
|
+
{"requesterAddress": requester},
|
|
2196
|
+
)
|
|
2197
|
+
self._broadcast("action_executed", f"🔓 Granted bounty access to {requester[:10]}...", {
|
|
2198
|
+
"action": "grant_bounty_access", "projectId": project_id, "bountyId": bounty_id,
|
|
2199
|
+
})
|
|
2200
|
+
except Exception as e:
|
|
2201
|
+
self._broadcast("error", f"✗ Failed to grant bounty access: {e}", {
|
|
2202
|
+
"action": "grant_bounty_access", "error": str(e),
|
|
2203
|
+
})
|
|
2204
|
+
else:
|
|
2205
|
+
# Denial is supervised — just log it
|
|
2206
|
+
if self._verbose:
|
|
2207
|
+
logger.info("[autonomous] Bounty access decision: DENY for %s (logged, not auto-denied)", requester[:10])
|
|
2208
|
+
|
|
2209
|
+
except Exception as exc:
|
|
2210
|
+
self._broadcast("error", f"✗ Bounty access request handling failed: {exc}", {
|
|
2211
|
+
"action": "bounty_access_requested", "projectId": project_id, "error": str(exc),
|
|
2212
|
+
})
|
|
2213
|
+
|
|
1955
2214
|
# ================================================================
|
|
1956
2215
|
# Action request handling (proactive.action.request)
|
|
1957
2216
|
# ================================================================
|
|
@@ -2013,7 +2272,7 @@ class AutonomousAgent:
|
|
|
2013
2272
|
tx_hash = pub.get("txHash") if isinstance(pub, dict) else getattr(pub, "tx_hash", None)
|
|
2014
2273
|
result = {"cid": pub.get("cid") if isinstance(pub, dict) else getattr(pub, "cid", None), "txHash": tx_hash}
|
|
2015
2274
|
|
|
2016
|
-
elif action_type
|
|
2275
|
+
elif action_type in ("create_post", "publish"):
|
|
2017
2276
|
community = payload.get("community", "general")
|
|
2018
2277
|
title = payload.get("title") or (suggested_content[:100] if suggested_content else "Untitled")
|
|
2019
2278
|
body = suggested_content or payload.get("body", "")
|
|
@@ -2160,7 +2419,7 @@ class AutonomousAgent:
|
|
|
2160
2419
|
}
|
|
2161
2420
|
)
|
|
2162
2421
|
|
|
2163
|
-
elif action_type
|
|
2422
|
+
elif action_type in ("gateway_commit", "commit_files"):
|
|
2164
2423
|
pid = payload.get("projectId")
|
|
2165
2424
|
files = payload.get("files")
|
|
2166
2425
|
msg = suggested_content or payload.get("message", "Autonomous commit")
|
|
@@ -2400,7 +2659,7 @@ class AutonomousAgent:
|
|
|
2400
2659
|
task_result = await self._runtime.projects.update_task(proj_id, tid, **kw)
|
|
2401
2660
|
result = task_result if isinstance(task_result, dict) else {"updated": True}
|
|
2402
2661
|
|
|
2403
|
-
elif action_type
|
|
2662
|
+
elif action_type in ("find_matching_agents", "find_agents"):
|
|
2404
2663
|
skills = payload.get("skills", [])
|
|
2405
2664
|
if not skills:
|
|
2406
2665
|
raise ValueError("find_matching_agents requires skills array")
|
|
@@ -2627,7 +2886,7 @@ class AutonomousAgent:
|
|
|
2627
2886
|
elif action_type == "create_listing":
|
|
2628
2887
|
# Alias for list_service
|
|
2629
2888
|
prep = await self._runtime._http.request("POST", "/v1/prepare/service/list", payload)
|
|
2630
|
-
relay = await self._sign_and_relay(prep)
|
|
2889
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2631
2890
|
tx_hash = relay.get("txHash", "")
|
|
2632
2891
|
result = relay
|
|
2633
2892
|
|
|
@@ -2636,7 +2895,7 @@ class AutonomousAgent:
|
|
|
2636
2895
|
if not gid:
|
|
2637
2896
|
raise ValueError("approve_guild requires guildId")
|
|
2638
2897
|
prep = await self._runtime._http.request("POST", f"/v1/prepare/guild/{gid}/approve", {})
|
|
2639
|
-
relay = await self._sign_and_relay(prep)
|
|
2898
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2640
2899
|
tx_hash = relay.get("txHash", "")
|
|
2641
2900
|
result = relay
|
|
2642
2901
|
|
|
@@ -2645,7 +2904,7 @@ class AutonomousAgent:
|
|
|
2645
2904
|
if not gid:
|
|
2646
2905
|
raise ValueError("reject_guild requires guildId")
|
|
2647
2906
|
prep = await self._runtime._http.request("POST", f"/v1/prepare/guild/{gid}/reject", {})
|
|
2648
|
-
relay = await self._sign_and_relay(prep)
|
|
2907
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2649
2908
|
tx_hash = relay.get("txHash", "")
|
|
2650
2909
|
result = relay
|
|
2651
2910
|
|
|
@@ -2654,7 +2913,104 @@ class AutonomousAgent:
|
|
|
2654
2913
|
if not gid:
|
|
2655
2914
|
raise ValueError("leave_guild requires guildId")
|
|
2656
2915
|
prep = await self._runtime._http.request("POST", f"/v1/prepare/guild/{gid}/leave", {})
|
|
2657
|
-
relay = await self._sign_and_relay(prep)
|
|
2916
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2917
|
+
tx_hash = relay.get("txHash", "")
|
|
2918
|
+
result = relay
|
|
2919
|
+
|
|
2920
|
+
# ── Bounty access grant/deny ───────────────────────
|
|
2921
|
+
elif action_type == "grant":
|
|
2922
|
+
proj_id = payload.get("projectId")
|
|
2923
|
+
bounty_id = payload.get("bountyId")
|
|
2924
|
+
req_id = payload.get("requestId")
|
|
2925
|
+
if not proj_id or not bounty_id or not req_id:
|
|
2926
|
+
raise ValueError("grant requires projectId, bountyId, requestId")
|
|
2927
|
+
result = await self._runtime._http.request(
|
|
2928
|
+
"POST", f"/v1/projects/{proj_id}/bounties/{bounty_id}/grant-access",
|
|
2929
|
+
{"requestId": req_id},
|
|
2930
|
+
)
|
|
2931
|
+
|
|
2932
|
+
elif action_type == "deny":
|
|
2933
|
+
proj_id = payload.get("projectId")
|
|
2934
|
+
bounty_id = payload.get("bountyId")
|
|
2935
|
+
req_id = payload.get("requestId")
|
|
2936
|
+
if not proj_id or not bounty_id or not req_id:
|
|
2937
|
+
raise ValueError("deny requires projectId, bountyId, requestId")
|
|
2938
|
+
result = await self._runtime._http.request(
|
|
2939
|
+
"POST", f"/v1/projects/{proj_id}/bounties/{bounty_id}/deny-access",
|
|
2940
|
+
{"requestId": req_id},
|
|
2941
|
+
)
|
|
2942
|
+
|
|
2943
|
+
# ── Team invitations ──────────────────────────────
|
|
2944
|
+
elif action_type == "accept_invitation":
|
|
2945
|
+
inv_id = payload.get("invitationId")
|
|
2946
|
+
if not inv_id:
|
|
2947
|
+
raise ValueError("accept_invitation requires invitationId")
|
|
2948
|
+
result = await self._runtime._http.request(
|
|
2949
|
+
"POST", f"/v1/teams/invitations/{inv_id}/accept", {},
|
|
2950
|
+
)
|
|
2951
|
+
|
|
2952
|
+
elif action_type == "decline_invitation":
|
|
2953
|
+
inv_id = payload.get("invitationId")
|
|
2954
|
+
if not inv_id:
|
|
2955
|
+
raise ValueError("decline_invitation requires invitationId")
|
|
2956
|
+
result = await self._runtime._http.request(
|
|
2957
|
+
"POST", f"/v1/teams/invitations/{inv_id}/decline", {},
|
|
2958
|
+
)
|
|
2959
|
+
|
|
2960
|
+
# ── Service update ────────────────────────────────
|
|
2961
|
+
elif action_type == "update_service":
|
|
2962
|
+
listing_id = payload.get("listingId")
|
|
2963
|
+
if not listing_id:
|
|
2964
|
+
raise ValueError("update_service requires listingId")
|
|
2965
|
+
prep = await self._runtime._http.request(
|
|
2966
|
+
"POST", "/v1/prepare/service/update", payload,
|
|
2967
|
+
)
|
|
2968
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2969
|
+
tx_hash = relay.get("txHash", "")
|
|
2970
|
+
result = relay
|
|
2971
|
+
|
|
2972
|
+
# ── Additional bounty lifecycle ───────────────────
|
|
2973
|
+
elif action_type == "approve_bounty_work":
|
|
2974
|
+
bounty_id = payload.get("bountyId")
|
|
2975
|
+
if not bounty_id:
|
|
2976
|
+
raise ValueError("approve_bounty_work requires bountyId")
|
|
2977
|
+
prep = await self._runtime._http.request(
|
|
2978
|
+
"POST", f"/v1/prepare/bounty/{bounty_id}/approve", {},
|
|
2979
|
+
)
|
|
2980
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2981
|
+
tx_hash = relay.get("txHash", "")
|
|
2982
|
+
result = relay
|
|
2983
|
+
|
|
2984
|
+
elif action_type == "dispute_bounty_work":
|
|
2985
|
+
bounty_id = payload.get("bountyId")
|
|
2986
|
+
if not bounty_id:
|
|
2987
|
+
raise ValueError("dispute_bounty_work requires bountyId")
|
|
2988
|
+
prep = await self._runtime._http.request(
|
|
2989
|
+
"POST", f"/v1/prepare/bounty/{bounty_id}/dispute", {},
|
|
2990
|
+
)
|
|
2991
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2992
|
+
tx_hash = relay.get("txHash", "")
|
|
2993
|
+
result = relay
|
|
2994
|
+
|
|
2995
|
+
elif action_type == "cancel_bounty":
|
|
2996
|
+
bounty_id = payload.get("bountyId")
|
|
2997
|
+
if not bounty_id:
|
|
2998
|
+
raise ValueError("cancel_bounty requires bountyId")
|
|
2999
|
+
prep = await self._runtime._http.request(
|
|
3000
|
+
"POST", f"/v1/prepare/bounty/{bounty_id}/cancel", {},
|
|
3001
|
+
)
|
|
3002
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
3003
|
+
tx_hash = relay.get("txHash", "")
|
|
3004
|
+
result = relay
|
|
3005
|
+
|
|
3006
|
+
elif action_type == "unclaim_bounty":
|
|
3007
|
+
bounty_id = payload.get("bountyId")
|
|
3008
|
+
if not bounty_id:
|
|
3009
|
+
raise ValueError("unclaim_bounty requires bountyId")
|
|
3010
|
+
prep = await self._runtime._http.request(
|
|
3011
|
+
"POST", f"/v1/prepare/bounty/{bounty_id}/unclaim", {},
|
|
3012
|
+
)
|
|
3013
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
2658
3014
|
tx_hash = relay.get("txHash", "")
|
|
2659
3015
|
result = relay
|
|
2660
3016
|
|
|
@@ -265,6 +265,18 @@ class _IdentityManager:
|
|
|
265
265
|
body["domains"] = domains
|
|
266
266
|
return await self._http.request("POST", "/v1/agents", body)
|
|
267
267
|
|
|
268
|
+
async def update_soul(self, deployment_id: str, soul_cid: str) -> dict[str, Any]:
|
|
269
|
+
"""Update the agent's soul CID (for agent launchpad deployments).
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
deployment_id: Deployment ID.
|
|
273
|
+
soul_cid: New IPFS CID for the soul document.
|
|
274
|
+
"""
|
|
275
|
+
return await self._http.request(
|
|
276
|
+
"PUT", f"/v1/deployments/{url_quote(deployment_id, safe='')}/soul",
|
|
277
|
+
{"soulCid": soul_cid},
|
|
278
|
+
)
|
|
279
|
+
|
|
268
280
|
|
|
269
281
|
class _MemoryBridge:
|
|
270
282
|
"""Publish and query knowledge on the Nookplot network."""
|
|
@@ -3088,6 +3100,53 @@ class _MarketplaceManager:
|
|
|
3088
3100
|
"comment": comment,
|
|
3089
3101
|
})
|
|
3090
3102
|
|
|
3103
|
+
async def get_agreement_messages(self, agreement_id: int) -> list[dict[str, Any]]:
|
|
3104
|
+
"""Get messages for an agreement.
|
|
3105
|
+
|
|
3106
|
+
Args:
|
|
3107
|
+
agreement_id: On-chain agreement ID.
|
|
3108
|
+
"""
|
|
3109
|
+
data = await self._http.request("GET", f"/v1/marketplace/agreements/{agreement_id}/messages")
|
|
3110
|
+
return data.get("messages", [])
|
|
3111
|
+
|
|
3112
|
+
async def send_agreement_message(
|
|
3113
|
+
self,
|
|
3114
|
+
agreement_id: int,
|
|
3115
|
+
content: str,
|
|
3116
|
+
message_type: str = "general",
|
|
3117
|
+
) -> dict[str, Any]:
|
|
3118
|
+
"""Send a message within an agreement.
|
|
3119
|
+
|
|
3120
|
+
Args:
|
|
3121
|
+
agreement_id: On-chain agreement ID.
|
|
3122
|
+
content: Message text.
|
|
3123
|
+
message_type: Type of message (``"general"``, ``"revision_request"``, ``"dispute_evidence"``).
|
|
3124
|
+
"""
|
|
3125
|
+
return await self._http.request("POST", f"/v1/marketplace/agreements/{agreement_id}/messages", {
|
|
3126
|
+
"content": content,
|
|
3127
|
+
"messageType": message_type,
|
|
3128
|
+
})
|
|
3129
|
+
|
|
3130
|
+
async def expire_dispute(self, agreement_id: int) -> dict[str, Any]:
|
|
3131
|
+
"""Expire a disputed agreement (auto-settle after dispute window).
|
|
3132
|
+
|
|
3133
|
+
Args:
|
|
3134
|
+
agreement_id: On-chain agreement ID.
|
|
3135
|
+
"""
|
|
3136
|
+
return await self._prepare_sign_relay("/v1/prepare/service/expire-dispute", {
|
|
3137
|
+
"agreementId": agreement_id,
|
|
3138
|
+
})
|
|
3139
|
+
|
|
3140
|
+
async def expire_delivered(self, agreement_id: int) -> dict[str, Any]:
|
|
3141
|
+
"""Expire a delivered agreement (auto-settle after delivery window).
|
|
3142
|
+
|
|
3143
|
+
Args:
|
|
3144
|
+
agreement_id: On-chain agreement ID.
|
|
3145
|
+
"""
|
|
3146
|
+
return await self._prepare_sign_relay("/v1/prepare/service/expire-delivered", {
|
|
3147
|
+
"agreementId": agreement_id,
|
|
3148
|
+
})
|
|
3149
|
+
|
|
3091
3150
|
|
|
3092
3151
|
class _TeachingManager:
|
|
3093
3152
|
"""Teaching exchange operations — propose, accept, deliver, approve/reject,
|
|
@@ -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.24"
|
|
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
|