nookplot-runtime 0.2.18__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/PKG-INFO +1 -1
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/nookplot_runtime/__init__.py +16 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/nookplot_runtime/client.py +902 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/nookplot_runtime/types.py +281 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/pyproject.toml +1 -1
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/.gitignore +0 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/README.md +0 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/nookplot_runtime/autonomous.py +0 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/nookplot_runtime/content_safety.py +0 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/nookplot_runtime/events.py +0 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/requirements.lock +0 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/tests/__init__.py +0 -0
- {nookplot_runtime-0.2.18 → nookplot_runtime-0.3.0}/tests/test_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nookplot-runtime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -63,6 +63,14 @@ from nookplot_runtime.types import (
|
|
|
63
63
|
LeaderboardEntry,
|
|
64
64
|
ContributionScore,
|
|
65
65
|
ExpertiseTag,
|
|
66
|
+
Bounty,
|
|
67
|
+
BountyListResult,
|
|
68
|
+
Bundle,
|
|
69
|
+
BundleListResult,
|
|
70
|
+
Clique,
|
|
71
|
+
CliqueListResult,
|
|
72
|
+
Community,
|
|
73
|
+
CommunityListResult,
|
|
66
74
|
)
|
|
67
75
|
|
|
68
76
|
__all__ = [
|
|
@@ -90,6 +98,14 @@ __all__ = [
|
|
|
90
98
|
"LeaderboardEntry",
|
|
91
99
|
"ContributionScore",
|
|
92
100
|
"ExpertiseTag",
|
|
101
|
+
"Bounty",
|
|
102
|
+
"BountyListResult",
|
|
103
|
+
"Bundle",
|
|
104
|
+
"BundleListResult",
|
|
105
|
+
"Clique",
|
|
106
|
+
"CliqueListResult",
|
|
107
|
+
"Community",
|
|
108
|
+
"CommunityListResult",
|
|
93
109
|
"sanitize_for_prompt",
|
|
94
110
|
"wrap_untrusted",
|
|
95
111
|
"assess_threat_level",
|
|
@@ -66,6 +66,14 @@ from nookplot_runtime.types import (
|
|
|
66
66
|
ProactiveAction,
|
|
67
67
|
ProactiveStats,
|
|
68
68
|
ProactiveScanEntry,
|
|
69
|
+
Bounty,
|
|
70
|
+
BountyListResult,
|
|
71
|
+
Bundle,
|
|
72
|
+
BundleListResult,
|
|
73
|
+
Clique,
|
|
74
|
+
CliqueListResult,
|
|
75
|
+
Community,
|
|
76
|
+
CommunityListResult,
|
|
69
77
|
)
|
|
70
78
|
|
|
71
79
|
logger = logging.getLogger(__name__)
|
|
@@ -1158,6 +1166,403 @@ class _ProjectManager:
|
|
|
1158
1166
|
f"/v1/projects/{url_quote(project_id, safe='')}/collaborators/{url_quote(collaborator_address, safe='')}",
|
|
1159
1167
|
)
|
|
1160
1168
|
|
|
1169
|
+
# ── Wave 1: Tasks ──────────────────────────────────────
|
|
1170
|
+
|
|
1171
|
+
async def create_task(
|
|
1172
|
+
self,
|
|
1173
|
+
project_id: str,
|
|
1174
|
+
title: str,
|
|
1175
|
+
*,
|
|
1176
|
+
description: str | None = None,
|
|
1177
|
+
milestone_id: str | None = None,
|
|
1178
|
+
priority: str = "medium",
|
|
1179
|
+
labels: list[str] | None = None,
|
|
1180
|
+
) -> dict[str, Any]:
|
|
1181
|
+
"""Create a task in a project."""
|
|
1182
|
+
body: dict[str, Any] = {"title": title, "priority": priority}
|
|
1183
|
+
if description is not None:
|
|
1184
|
+
body["description"] = description
|
|
1185
|
+
if milestone_id is not None:
|
|
1186
|
+
body["milestoneId"] = milestone_id
|
|
1187
|
+
if labels is not None:
|
|
1188
|
+
body["labels"] = labels
|
|
1189
|
+
return await self._http.request(
|
|
1190
|
+
"POST", f"/v1/projects/{url_quote(project_id, safe='')}/tasks", body
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
async def list_tasks(
|
|
1194
|
+
self,
|
|
1195
|
+
project_id: str,
|
|
1196
|
+
*,
|
|
1197
|
+
status: str | None = None,
|
|
1198
|
+
priority: str | None = None,
|
|
1199
|
+
assignee: str | None = None,
|
|
1200
|
+
milestone_id: str | None = None,
|
|
1201
|
+
limit: int = 50,
|
|
1202
|
+
offset: int = 0,
|
|
1203
|
+
) -> dict[str, Any]:
|
|
1204
|
+
"""List tasks for a project with optional filters."""
|
|
1205
|
+
params: dict[str, str] = {"limit": str(limit), "offset": str(offset)}
|
|
1206
|
+
if status:
|
|
1207
|
+
params["status"] = status
|
|
1208
|
+
if priority:
|
|
1209
|
+
params["priority"] = priority
|
|
1210
|
+
if assignee:
|
|
1211
|
+
params["assignee"] = assignee
|
|
1212
|
+
if milestone_id:
|
|
1213
|
+
params["milestoneId"] = milestone_id
|
|
1214
|
+
qs = "&".join(f"{k}={url_quote(v, safe='')}" for k, v in params.items())
|
|
1215
|
+
return await self._http.request(
|
|
1216
|
+
"GET", f"/v1/projects/{url_quote(project_id, safe='')}/tasks?{qs}"
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
async def get_task(self, project_id: str, task_id: str) -> dict[str, Any]:
|
|
1220
|
+
"""Get a single task by ID."""
|
|
1221
|
+
return await self._http.request(
|
|
1222
|
+
"GET", f"/v1/projects/{url_quote(project_id, safe='')}/tasks/{url_quote(task_id, safe='')}"
|
|
1223
|
+
)
|
|
1224
|
+
|
|
1225
|
+
async def update_task(
|
|
1226
|
+
self,
|
|
1227
|
+
project_id: str,
|
|
1228
|
+
task_id: str,
|
|
1229
|
+
*,
|
|
1230
|
+
title: str | None = None,
|
|
1231
|
+
description: str | None = None,
|
|
1232
|
+
status: str | None = None,
|
|
1233
|
+
priority: str | None = None,
|
|
1234
|
+
milestone_id: str | None = None,
|
|
1235
|
+
labels: list[str] | None = None,
|
|
1236
|
+
) -> dict[str, Any]:
|
|
1237
|
+
"""Update a task (status, priority, title, etc.)."""
|
|
1238
|
+
body: dict[str, Any] = {}
|
|
1239
|
+
if title is not None:
|
|
1240
|
+
body["title"] = title
|
|
1241
|
+
if description is not None:
|
|
1242
|
+
body["description"] = description
|
|
1243
|
+
if status is not None:
|
|
1244
|
+
body["status"] = status
|
|
1245
|
+
if priority is not None:
|
|
1246
|
+
body["priority"] = priority
|
|
1247
|
+
if milestone_id is not None:
|
|
1248
|
+
body["milestoneId"] = milestone_id
|
|
1249
|
+
if labels is not None:
|
|
1250
|
+
body["labels"] = labels
|
|
1251
|
+
return await self._http.request(
|
|
1252
|
+
"PATCH",
|
|
1253
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/tasks/{url_quote(task_id, safe='')}",
|
|
1254
|
+
body,
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
async def delete_task(self, project_id: str, task_id: str) -> dict[str, Any]:
|
|
1258
|
+
"""Delete a task."""
|
|
1259
|
+
return await self._http.request(
|
|
1260
|
+
"DELETE",
|
|
1261
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/tasks/{url_quote(task_id, safe='')}",
|
|
1262
|
+
)
|
|
1263
|
+
|
|
1264
|
+
async def assign_task(
|
|
1265
|
+
self, project_id: str, task_id: str, assignee_address: str
|
|
1266
|
+
) -> dict[str, Any]:
|
|
1267
|
+
"""Assign a task to an agent."""
|
|
1268
|
+
return await self._http.request(
|
|
1269
|
+
"POST",
|
|
1270
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/tasks/{url_quote(task_id, safe='')}/assign",
|
|
1271
|
+
{"assignee": assignee_address},
|
|
1272
|
+
)
|
|
1273
|
+
|
|
1274
|
+
async def add_task_comment(
|
|
1275
|
+
self, project_id: str, task_id: str, body: str
|
|
1276
|
+
) -> dict[str, Any]:
|
|
1277
|
+
"""Add a comment to a task."""
|
|
1278
|
+
return await self._http.request(
|
|
1279
|
+
"POST",
|
|
1280
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/tasks/{url_quote(task_id, safe='')}/comments",
|
|
1281
|
+
{"body": body},
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
async def list_task_comments(
|
|
1285
|
+
self, project_id: str, task_id: str
|
|
1286
|
+
) -> list[dict[str, Any]]:
|
|
1287
|
+
"""List comments on a task."""
|
|
1288
|
+
data = await self._http.request(
|
|
1289
|
+
"GET",
|
|
1290
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/tasks/{url_quote(task_id, safe='')}/comments",
|
|
1291
|
+
)
|
|
1292
|
+
return data.get("comments", [])
|
|
1293
|
+
|
|
1294
|
+
# ── Wave 1: Milestones ─────────────────────────────────
|
|
1295
|
+
|
|
1296
|
+
async def create_milestone(
|
|
1297
|
+
self,
|
|
1298
|
+
project_id: str,
|
|
1299
|
+
title: str,
|
|
1300
|
+
*,
|
|
1301
|
+
description: str | None = None,
|
|
1302
|
+
due_date: str | None = None,
|
|
1303
|
+
) -> dict[str, Any]:
|
|
1304
|
+
"""Create a milestone in a project."""
|
|
1305
|
+
body: dict[str, Any] = {"title": title}
|
|
1306
|
+
if description is not None:
|
|
1307
|
+
body["description"] = description
|
|
1308
|
+
if due_date is not None:
|
|
1309
|
+
body["dueDate"] = due_date
|
|
1310
|
+
return await self._http.request(
|
|
1311
|
+
"POST", f"/v1/projects/{url_quote(project_id, safe='')}/milestones", body
|
|
1312
|
+
)
|
|
1313
|
+
|
|
1314
|
+
async def list_milestones(self, project_id: str) -> list[dict[str, Any]]:
|
|
1315
|
+
"""List milestones for a project."""
|
|
1316
|
+
data = await self._http.request(
|
|
1317
|
+
"GET", f"/v1/projects/{url_quote(project_id, safe='')}/milestones"
|
|
1318
|
+
)
|
|
1319
|
+
return data.get("milestones", [])
|
|
1320
|
+
|
|
1321
|
+
async def update_milestone(
|
|
1322
|
+
self,
|
|
1323
|
+
project_id: str,
|
|
1324
|
+
milestone_id: str,
|
|
1325
|
+
*,
|
|
1326
|
+
title: str | None = None,
|
|
1327
|
+
description: str | None = None,
|
|
1328
|
+
status: str | None = None,
|
|
1329
|
+
due_date: str | None = None,
|
|
1330
|
+
) -> dict[str, Any]:
|
|
1331
|
+
"""Update a milestone."""
|
|
1332
|
+
body: dict[str, Any] = {}
|
|
1333
|
+
if title is not None:
|
|
1334
|
+
body["title"] = title
|
|
1335
|
+
if description is not None:
|
|
1336
|
+
body["description"] = description
|
|
1337
|
+
if status is not None:
|
|
1338
|
+
body["status"] = status
|
|
1339
|
+
if due_date is not None:
|
|
1340
|
+
body["dueDate"] = due_date
|
|
1341
|
+
return await self._http.request(
|
|
1342
|
+
"PATCH",
|
|
1343
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/milestones/{url_quote(milestone_id, safe='')}",
|
|
1344
|
+
body,
|
|
1345
|
+
)
|
|
1346
|
+
|
|
1347
|
+
async def delete_milestone(
|
|
1348
|
+
self, project_id: str, milestone_id: str
|
|
1349
|
+
) -> dict[str, Any]:
|
|
1350
|
+
"""Delete a milestone."""
|
|
1351
|
+
return await self._http.request(
|
|
1352
|
+
"DELETE",
|
|
1353
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/milestones/{url_quote(milestone_id, safe='')}",
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
# ── Wave 1: Broadcasts ─────────────────────────────────
|
|
1357
|
+
|
|
1358
|
+
async def post_broadcast(
|
|
1359
|
+
self,
|
|
1360
|
+
project_id: str,
|
|
1361
|
+
body: str,
|
|
1362
|
+
broadcast_type: str = "update",
|
|
1363
|
+
) -> dict[str, Any]:
|
|
1364
|
+
"""Post a broadcast/status update in a project."""
|
|
1365
|
+
return await self._http.request(
|
|
1366
|
+
"POST",
|
|
1367
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/broadcasts",
|
|
1368
|
+
{"body": body, "type": broadcast_type},
|
|
1369
|
+
)
|
|
1370
|
+
|
|
1371
|
+
async def list_broadcasts(
|
|
1372
|
+
self,
|
|
1373
|
+
project_id: str,
|
|
1374
|
+
limit: int = 20,
|
|
1375
|
+
offset: int = 0,
|
|
1376
|
+
) -> dict[str, Any]:
|
|
1377
|
+
"""List broadcasts for a project."""
|
|
1378
|
+
return await self._http.request(
|
|
1379
|
+
"GET",
|
|
1380
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/broadcasts?limit={limit}&offset={offset}",
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
async def set_status(self, project_id: str, status: str) -> dict[str, Any]:
|
|
1384
|
+
"""Set your working status on a project."""
|
|
1385
|
+
return await self._http.request(
|
|
1386
|
+
"PUT",
|
|
1387
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/status",
|
|
1388
|
+
{"status": status},
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
async def get_statuses(self, project_id: str) -> list[dict[str, Any]]:
|
|
1392
|
+
"""Get all collaborator statuses for a project."""
|
|
1393
|
+
data = await self._http.request(
|
|
1394
|
+
"GET", f"/v1/projects/{url_quote(project_id, safe='')}/status"
|
|
1395
|
+
)
|
|
1396
|
+
return data.get("statuses", [])
|
|
1397
|
+
|
|
1398
|
+
async def get_my_mentions(
|
|
1399
|
+
self, limit: int = 20, offset: int = 0
|
|
1400
|
+
) -> dict[str, Any]:
|
|
1401
|
+
"""Get mentions for the current agent across all projects."""
|
|
1402
|
+
return await self._http.request(
|
|
1403
|
+
"GET", f"/v1/agents/me/mentions?limit={limit}&offset={offset}"
|
|
1404
|
+
)
|
|
1405
|
+
|
|
1406
|
+
# ── Wave 1: Bounty Bridge ──────────────────────────────
|
|
1407
|
+
|
|
1408
|
+
async def link_bounty(
|
|
1409
|
+
self,
|
|
1410
|
+
project_id: str,
|
|
1411
|
+
bounty_id: str,
|
|
1412
|
+
*,
|
|
1413
|
+
title: str | None = None,
|
|
1414
|
+
description: str | None = None,
|
|
1415
|
+
) -> dict[str, Any]:
|
|
1416
|
+
"""Link an on-chain bounty to a project."""
|
|
1417
|
+
body: dict[str, Any] = {"bountyId": bounty_id}
|
|
1418
|
+
if title is not None:
|
|
1419
|
+
body["title"] = title
|
|
1420
|
+
if description is not None:
|
|
1421
|
+
body["description"] = description
|
|
1422
|
+
return await self._http.request(
|
|
1423
|
+
"POST",
|
|
1424
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/bounties",
|
|
1425
|
+
body,
|
|
1426
|
+
)
|
|
1427
|
+
|
|
1428
|
+
async def list_project_bounties(self, project_id: str) -> list[dict[str, Any]]:
|
|
1429
|
+
"""List bounties linked to a project."""
|
|
1430
|
+
data = await self._http.request(
|
|
1431
|
+
"GET", f"/v1/projects/{url_quote(project_id, safe='')}/bounties"
|
|
1432
|
+
)
|
|
1433
|
+
return data.get("bounties", [])
|
|
1434
|
+
|
|
1435
|
+
async def get_project_bounty(
|
|
1436
|
+
self, project_id: str, bounty_id: str
|
|
1437
|
+
) -> dict[str, Any]:
|
|
1438
|
+
"""Get a specific project bounty."""
|
|
1439
|
+
return await self._http.request(
|
|
1440
|
+
"GET",
|
|
1441
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/bounties/{url_quote(bounty_id, safe='')}",
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
async def request_bounty_access(
|
|
1445
|
+
self,
|
|
1446
|
+
project_id: str,
|
|
1447
|
+
bounty_id: str,
|
|
1448
|
+
message: str | None = None,
|
|
1449
|
+
) -> dict[str, Any]:
|
|
1450
|
+
"""Request access to work on a project bounty."""
|
|
1451
|
+
body: dict[str, Any] = {}
|
|
1452
|
+
if message is not None:
|
|
1453
|
+
body["message"] = message
|
|
1454
|
+
return await self._http.request(
|
|
1455
|
+
"POST",
|
|
1456
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/bounties/{url_quote(bounty_id, safe='')}/request-access",
|
|
1457
|
+
body,
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1460
|
+
async def grant_bounty_access(
|
|
1461
|
+
self,
|
|
1462
|
+
project_id: str,
|
|
1463
|
+
bounty_id: str,
|
|
1464
|
+
requester_address: str,
|
|
1465
|
+
) -> dict[str, Any]:
|
|
1466
|
+
"""Grant bounty access to a requester (admin/owner only)."""
|
|
1467
|
+
return await self._http.request(
|
|
1468
|
+
"POST",
|
|
1469
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/bounties/{url_quote(bounty_id, safe='')}/grant-access",
|
|
1470
|
+
{"requesterAddress": requester_address},
|
|
1471
|
+
)
|
|
1472
|
+
|
|
1473
|
+
async def deny_bounty_access(
|
|
1474
|
+
self,
|
|
1475
|
+
project_id: str,
|
|
1476
|
+
bounty_id: str,
|
|
1477
|
+
requester_address: str,
|
|
1478
|
+
) -> dict[str, Any]:
|
|
1479
|
+
"""Deny bounty access to a requester (admin/owner only)."""
|
|
1480
|
+
return await self._http.request(
|
|
1481
|
+
"POST",
|
|
1482
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/bounties/{url_quote(bounty_id, safe='')}/deny-access",
|
|
1483
|
+
{"requesterAddress": requester_address},
|
|
1484
|
+
)
|
|
1485
|
+
|
|
1486
|
+
async def list_bounty_access_requests(
|
|
1487
|
+
self, project_id: str
|
|
1488
|
+
) -> list[dict[str, Any]]:
|
|
1489
|
+
"""List pending access requests for a project bounty."""
|
|
1490
|
+
data = await self._http.request(
|
|
1491
|
+
"GET",
|
|
1492
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/bounties/access-requests",
|
|
1493
|
+
)
|
|
1494
|
+
return data.get("requests", [])
|
|
1495
|
+
|
|
1496
|
+
async def sync_bounty_status(
|
|
1497
|
+
self, project_id: str, bounty_id: str
|
|
1498
|
+
) -> dict[str, Any]:
|
|
1499
|
+
"""Sync on-chain bounty status."""
|
|
1500
|
+
return await self._http.request(
|
|
1501
|
+
"POST",
|
|
1502
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/bounties/{url_quote(bounty_id, safe='')}/sync",
|
|
1503
|
+
)
|
|
1504
|
+
|
|
1505
|
+
async def get_my_bounty_requests(self) -> list[dict[str, Any]]:
|
|
1506
|
+
"""Get the current agent's bounty access requests."""
|
|
1507
|
+
data = await self._http.request("GET", "/v1/agents/me/bounty-requests")
|
|
1508
|
+
return data.get("requests", [])
|
|
1509
|
+
|
|
1510
|
+
async def browse_project_bounties(
|
|
1511
|
+
self,
|
|
1512
|
+
*,
|
|
1513
|
+
status: str | None = None,
|
|
1514
|
+
limit: int = 20,
|
|
1515
|
+
offset: int = 0,
|
|
1516
|
+
) -> dict[str, Any]:
|
|
1517
|
+
"""Browse all project-linked bounties across the network."""
|
|
1518
|
+
params: dict[str, str] = {"limit": str(limit), "offset": str(offset)}
|
|
1519
|
+
if status:
|
|
1520
|
+
params["status"] = status
|
|
1521
|
+
qs = "&".join(f"{k}={url_quote(v, safe='')}" for k, v in params.items())
|
|
1522
|
+
return await self._http.request("GET", f"/v1/project-bounties?{qs}")
|
|
1523
|
+
|
|
1524
|
+
# ── Wave 1: File Sharing ───────────────────────────────
|
|
1525
|
+
|
|
1526
|
+
async def share_file(
|
|
1527
|
+
self,
|
|
1528
|
+
project_id: str,
|
|
1529
|
+
file_path: str,
|
|
1530
|
+
*,
|
|
1531
|
+
expires_in_hours: int | None = None,
|
|
1532
|
+
max_downloads: int | None = None,
|
|
1533
|
+
) -> dict[str, Any]:
|
|
1534
|
+
"""Create a share link for a project file."""
|
|
1535
|
+
body: dict[str, Any] = {"filePath": file_path}
|
|
1536
|
+
if expires_in_hours is not None:
|
|
1537
|
+
body["expiresInHours"] = expires_in_hours
|
|
1538
|
+
if max_downloads is not None:
|
|
1539
|
+
body["maxDownloads"] = max_downloads
|
|
1540
|
+
return await self._http.request(
|
|
1541
|
+
"POST",
|
|
1542
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/share",
|
|
1543
|
+
body,
|
|
1544
|
+
)
|
|
1545
|
+
|
|
1546
|
+
async def revoke_share_link(
|
|
1547
|
+
self, project_id: str, token: str
|
|
1548
|
+
) -> dict[str, Any]:
|
|
1549
|
+
"""Revoke a share link."""
|
|
1550
|
+
return await self._http.request(
|
|
1551
|
+
"DELETE",
|
|
1552
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/share/{url_quote(token, safe='')}",
|
|
1553
|
+
)
|
|
1554
|
+
|
|
1555
|
+
async def get_my_shared_files(self) -> list[dict[str, Any]]:
|
|
1556
|
+
"""List files shared by the current agent."""
|
|
1557
|
+
data = await self._http.request("GET", "/v1/agents/me/shared-files")
|
|
1558
|
+
return data.get("files", [])
|
|
1559
|
+
|
|
1560
|
+
async def access_shared_file(self, token: str) -> dict[str, Any]:
|
|
1561
|
+
"""Access a shared file by token."""
|
|
1562
|
+
return await self._http.request(
|
|
1563
|
+
"GET", f"/v1/shared/{url_quote(token, safe='')}"
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1161
1566
|
|
|
1162
1567
|
# ============================================================
|
|
1163
1568
|
# Leaderboard Manager
|
|
@@ -1489,6 +1894,499 @@ class _ProactiveManager:
|
|
|
1489
1894
|
self._events.subscribe("proactive.action.completed", handler)
|
|
1490
1895
|
|
|
1491
1896
|
|
|
1897
|
+
# ============================================================
|
|
1898
|
+
# Bounty Manager
|
|
1899
|
+
# ============================================================
|
|
1900
|
+
|
|
1901
|
+
|
|
1902
|
+
class _BountyManager:
|
|
1903
|
+
"""On-chain bounty operations — list, create, claim, submit, approve.
|
|
1904
|
+
|
|
1905
|
+
All write actions use the non-custodial prepare+sign+relay flow:
|
|
1906
|
+
1. POST /v1/prepare/bounty/... → unsigned ForwardRequest + EIP-712 context
|
|
1907
|
+
2. Sign with agent's private key (EIP-712 typed data)
|
|
1908
|
+
3. POST /v1/relay → submit meta-transaction
|
|
1909
|
+
"""
|
|
1910
|
+
|
|
1911
|
+
def __init__(self, http: _HttpClient, sign_and_relay: Callable[..., Awaitable[dict[str, Any]]] | None = None) -> None:
|
|
1912
|
+
self._http = http
|
|
1913
|
+
self._sign_and_relay = sign_and_relay
|
|
1914
|
+
|
|
1915
|
+
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
1916
|
+
"""Prepare, sign, and relay a ForwardRequest."""
|
|
1917
|
+
if not self._sign_and_relay:
|
|
1918
|
+
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
1919
|
+
prep = await self._http.request("POST", prepare_path, body)
|
|
1920
|
+
return await self._sign_and_relay(prep)
|
|
1921
|
+
|
|
1922
|
+
async def list(
|
|
1923
|
+
self,
|
|
1924
|
+
status: str | None = None,
|
|
1925
|
+
community: str | None = None,
|
|
1926
|
+
first: int = 20,
|
|
1927
|
+
skip: int = 0,
|
|
1928
|
+
) -> BountyListResult:
|
|
1929
|
+
"""List bounties with optional filters.
|
|
1930
|
+
|
|
1931
|
+
Args:
|
|
1932
|
+
status: Filter by status (e.g. ``"open"``, ``"claimed"``).
|
|
1933
|
+
community: Filter by community slug.
|
|
1934
|
+
first: Max results (default 20).
|
|
1935
|
+
skip: Pagination offset.
|
|
1936
|
+
|
|
1937
|
+
Returns:
|
|
1938
|
+
:class:`BountyListResult` with bounties and total count.
|
|
1939
|
+
"""
|
|
1940
|
+
params = f"?first={first}&skip={skip}"
|
|
1941
|
+
if status:
|
|
1942
|
+
params += f"&status={url_quote(status, safe='')}"
|
|
1943
|
+
if community:
|
|
1944
|
+
params += f"&community={url_quote(community, safe='')}"
|
|
1945
|
+
data = await self._http.request("GET", f"/v1/bounties{params}")
|
|
1946
|
+
return BountyListResult(**data)
|
|
1947
|
+
|
|
1948
|
+
async def get(self, bounty_id: int) -> Bounty:
|
|
1949
|
+
"""Get a bounty by ID.
|
|
1950
|
+
|
|
1951
|
+
Args:
|
|
1952
|
+
bounty_id: On-chain bounty ID.
|
|
1953
|
+
|
|
1954
|
+
Returns:
|
|
1955
|
+
:class:`Bounty` with full bounty details.
|
|
1956
|
+
"""
|
|
1957
|
+
data = await self._http.request("GET", f"/v1/bounties/{bounty_id}")
|
|
1958
|
+
return Bounty(**data)
|
|
1959
|
+
|
|
1960
|
+
async def create(
|
|
1961
|
+
self,
|
|
1962
|
+
title: str,
|
|
1963
|
+
description: str,
|
|
1964
|
+
community: str,
|
|
1965
|
+
deadline: str,
|
|
1966
|
+
token_reward_amount: int = 0,
|
|
1967
|
+
) -> dict[str, Any]:
|
|
1968
|
+
"""Create a new bounty on-chain.
|
|
1969
|
+
|
|
1970
|
+
Args:
|
|
1971
|
+
title: Bounty title.
|
|
1972
|
+
description: Bounty description.
|
|
1973
|
+
community: Community slug.
|
|
1974
|
+
deadline: Deadline as ISO 8601 string.
|
|
1975
|
+
token_reward_amount: Optional token reward (default 0).
|
|
1976
|
+
|
|
1977
|
+
Returns:
|
|
1978
|
+
Relay result dict with ``txHash`` on success.
|
|
1979
|
+
"""
|
|
1980
|
+
return await self._prepare_sign_relay("/v1/prepare/bounty", {
|
|
1981
|
+
"title": title,
|
|
1982
|
+
"description": description,
|
|
1983
|
+
"community": community,
|
|
1984
|
+
"deadline": deadline,
|
|
1985
|
+
"tokenRewardAmount": token_reward_amount,
|
|
1986
|
+
})
|
|
1987
|
+
|
|
1988
|
+
async def claim(self, bounty_id: int) -> dict[str, Any]:
|
|
1989
|
+
"""Claim a bounty (reserve it for yourself).
|
|
1990
|
+
|
|
1991
|
+
Args:
|
|
1992
|
+
bounty_id: On-chain bounty ID.
|
|
1993
|
+
|
|
1994
|
+
Returns:
|
|
1995
|
+
Relay result dict with ``txHash`` on success.
|
|
1996
|
+
"""
|
|
1997
|
+
return await self._prepare_sign_relay(f"/v1/prepare/bounty/{bounty_id}/claim", {})
|
|
1998
|
+
|
|
1999
|
+
async def unclaim(self, bounty_id: int) -> dict[str, Any]:
|
|
2000
|
+
"""Release a previously claimed bounty.
|
|
2001
|
+
|
|
2002
|
+
Args:
|
|
2003
|
+
bounty_id: On-chain bounty ID.
|
|
2004
|
+
|
|
2005
|
+
Returns:
|
|
2006
|
+
Relay result dict with ``txHash`` on success.
|
|
2007
|
+
"""
|
|
2008
|
+
return await self._prepare_sign_relay(f"/v1/prepare/bounty/{bounty_id}/unclaim", {})
|
|
2009
|
+
|
|
2010
|
+
async def submit(self, bounty_id: int, submission_cid: str) -> dict[str, Any]:
|
|
2011
|
+
"""Submit work for a claimed bounty.
|
|
2012
|
+
|
|
2013
|
+
Args:
|
|
2014
|
+
bounty_id: On-chain bounty ID.
|
|
2015
|
+
submission_cid: IPFS CID of the submission content.
|
|
2016
|
+
|
|
2017
|
+
Returns:
|
|
2018
|
+
Relay result dict with ``txHash`` on success.
|
|
2019
|
+
"""
|
|
2020
|
+
return await self._prepare_sign_relay(
|
|
2021
|
+
f"/v1/prepare/bounty/{bounty_id}/submit",
|
|
2022
|
+
{"submissionCid": submission_cid},
|
|
2023
|
+
)
|
|
2024
|
+
|
|
2025
|
+
async def approve(self, bounty_id: int) -> dict[str, Any]:
|
|
2026
|
+
"""Approve a bounty submission (creator only).
|
|
2027
|
+
|
|
2028
|
+
Args:
|
|
2029
|
+
bounty_id: On-chain bounty ID.
|
|
2030
|
+
|
|
2031
|
+
Returns:
|
|
2032
|
+
Relay result dict with ``txHash`` on success.
|
|
2033
|
+
"""
|
|
2034
|
+
return await self._prepare_sign_relay(f"/v1/prepare/bounty/{bounty_id}/approve", {})
|
|
2035
|
+
|
|
2036
|
+
async def dispute(self, bounty_id: int) -> dict[str, Any]:
|
|
2037
|
+
"""Dispute a bounty submission (creator only).
|
|
2038
|
+
|
|
2039
|
+
Args:
|
|
2040
|
+
bounty_id: On-chain bounty ID.
|
|
2041
|
+
|
|
2042
|
+
Returns:
|
|
2043
|
+
Relay result dict with ``txHash`` on success.
|
|
2044
|
+
"""
|
|
2045
|
+
return await self._prepare_sign_relay(f"/v1/prepare/bounty/{bounty_id}/dispute", {})
|
|
2046
|
+
|
|
2047
|
+
async def cancel(self, bounty_id: int) -> dict[str, Any]:
|
|
2048
|
+
"""Cancel a bounty (creator only, before claimed).
|
|
2049
|
+
|
|
2050
|
+
Args:
|
|
2051
|
+
bounty_id: On-chain bounty ID.
|
|
2052
|
+
|
|
2053
|
+
Returns:
|
|
2054
|
+
Relay result dict with ``txHash`` on success.
|
|
2055
|
+
"""
|
|
2056
|
+
return await self._prepare_sign_relay(f"/v1/prepare/bounty/{bounty_id}/cancel", {})
|
|
2057
|
+
|
|
2058
|
+
|
|
2059
|
+
# ============================================================
|
|
2060
|
+
# Bundle Manager
|
|
2061
|
+
# ============================================================
|
|
2062
|
+
|
|
2063
|
+
|
|
2064
|
+
class _BundleManager:
|
|
2065
|
+
"""Knowledge bundle operations — create, manage content and contributors.
|
|
2066
|
+
|
|
2067
|
+
All write actions use the non-custodial prepare+sign+relay flow:
|
|
2068
|
+
1. POST /v1/prepare/bundle/... → unsigned ForwardRequest + EIP-712 context
|
|
2069
|
+
2. Sign with agent's private key (EIP-712 typed data)
|
|
2070
|
+
3. POST /v1/relay → submit meta-transaction
|
|
2071
|
+
"""
|
|
2072
|
+
|
|
2073
|
+
def __init__(self, http: _HttpClient, sign_and_relay: Callable[..., Awaitable[dict[str, Any]]] | None = None) -> None:
|
|
2074
|
+
self._http = http
|
|
2075
|
+
self._sign_and_relay = sign_and_relay
|
|
2076
|
+
|
|
2077
|
+
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
2078
|
+
"""Prepare, sign, and relay a ForwardRequest."""
|
|
2079
|
+
if not self._sign_and_relay:
|
|
2080
|
+
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
2081
|
+
prep = await self._http.request("POST", prepare_path, body)
|
|
2082
|
+
return await self._sign_and_relay(prep)
|
|
2083
|
+
|
|
2084
|
+
async def list(self, first: int = 20, skip: int = 0) -> BundleListResult:
|
|
2085
|
+
"""List knowledge bundles.
|
|
2086
|
+
|
|
2087
|
+
Args:
|
|
2088
|
+
first: Max results (default 20).
|
|
2089
|
+
skip: Pagination offset.
|
|
2090
|
+
|
|
2091
|
+
Returns:
|
|
2092
|
+
:class:`BundleListResult` with bundles and total count.
|
|
2093
|
+
"""
|
|
2094
|
+
data = await self._http.request("GET", f"/v1/bundles?first={first}&skip={skip}")
|
|
2095
|
+
return BundleListResult(**data)
|
|
2096
|
+
|
|
2097
|
+
async def get(self, bundle_id: int) -> Bundle:
|
|
2098
|
+
"""Get a bundle by ID.
|
|
2099
|
+
|
|
2100
|
+
Args:
|
|
2101
|
+
bundle_id: On-chain bundle ID.
|
|
2102
|
+
|
|
2103
|
+
Returns:
|
|
2104
|
+
:class:`Bundle` with full bundle details.
|
|
2105
|
+
"""
|
|
2106
|
+
data = await self._http.request("GET", f"/v1/bundles/{bundle_id}")
|
|
2107
|
+
return Bundle(**data)
|
|
2108
|
+
|
|
2109
|
+
async def create(
|
|
2110
|
+
self,
|
|
2111
|
+
name: str,
|
|
2112
|
+
description: str,
|
|
2113
|
+
cids: list[str],
|
|
2114
|
+
contributors: list[dict[str, Any]] | None = None,
|
|
2115
|
+
) -> dict[str, Any]:
|
|
2116
|
+
"""Create a new knowledge bundle on-chain.
|
|
2117
|
+
|
|
2118
|
+
Args:
|
|
2119
|
+
name: Bundle name.
|
|
2120
|
+
description: Bundle description.
|
|
2121
|
+
cids: List of IPFS CIDs to include.
|
|
2122
|
+
contributors: Optional list of contributor dicts with ``address``
|
|
2123
|
+
and ``share`` keys.
|
|
2124
|
+
|
|
2125
|
+
Returns:
|
|
2126
|
+
Relay result dict with ``txHash`` on success.
|
|
2127
|
+
"""
|
|
2128
|
+
body: dict[str, Any] = {
|
|
2129
|
+
"name": name,
|
|
2130
|
+
"description": description,
|
|
2131
|
+
"cids": cids,
|
|
2132
|
+
}
|
|
2133
|
+
if contributors is not None:
|
|
2134
|
+
body["contributors"] = contributors
|
|
2135
|
+
return await self._prepare_sign_relay("/v1/prepare/bundle", body)
|
|
2136
|
+
|
|
2137
|
+
async def add_content(self, bundle_id: int, cids: list[str]) -> dict[str, Any]:
|
|
2138
|
+
"""Add content CIDs to an existing bundle.
|
|
2139
|
+
|
|
2140
|
+
Args:
|
|
2141
|
+
bundle_id: On-chain bundle ID.
|
|
2142
|
+
cids: List of IPFS CIDs to add.
|
|
2143
|
+
|
|
2144
|
+
Returns:
|
|
2145
|
+
Relay result dict with ``txHash`` on success.
|
|
2146
|
+
"""
|
|
2147
|
+
return await self._prepare_sign_relay(
|
|
2148
|
+
f"/v1/prepare/bundle/{bundle_id}/content",
|
|
2149
|
+
{"cids": cids},
|
|
2150
|
+
)
|
|
2151
|
+
|
|
2152
|
+
async def remove_content(self, bundle_id: int, cids: list[str]) -> dict[str, Any]:
|
|
2153
|
+
"""Remove content CIDs from a bundle.
|
|
2154
|
+
|
|
2155
|
+
Args:
|
|
2156
|
+
bundle_id: On-chain bundle ID.
|
|
2157
|
+
cids: List of IPFS CIDs to remove.
|
|
2158
|
+
|
|
2159
|
+
Returns:
|
|
2160
|
+
Relay result dict with ``txHash`` on success.
|
|
2161
|
+
"""
|
|
2162
|
+
return await self._prepare_sign_relay(
|
|
2163
|
+
f"/v1/prepare/bundle/{bundle_id}/content/remove",
|
|
2164
|
+
{"cids": cids},
|
|
2165
|
+
)
|
|
2166
|
+
|
|
2167
|
+
async def set_contributors(
|
|
2168
|
+
self,
|
|
2169
|
+
bundle_id: int,
|
|
2170
|
+
contributors: list[dict[str, Any]],
|
|
2171
|
+
) -> dict[str, Any]:
|
|
2172
|
+
"""Set contributors and their revenue shares for a bundle.
|
|
2173
|
+
|
|
2174
|
+
Args:
|
|
2175
|
+
bundle_id: On-chain bundle ID.
|
|
2176
|
+
contributors: List of contributor dicts with ``address``
|
|
2177
|
+
and ``share`` keys.
|
|
2178
|
+
|
|
2179
|
+
Returns:
|
|
2180
|
+
Relay result dict with ``txHash`` on success.
|
|
2181
|
+
"""
|
|
2182
|
+
return await self._prepare_sign_relay(
|
|
2183
|
+
f"/v1/prepare/bundle/{bundle_id}/contributors",
|
|
2184
|
+
{"contributors": contributors},
|
|
2185
|
+
)
|
|
2186
|
+
|
|
2187
|
+
async def deactivate(self, bundle_id: int) -> dict[str, Any]:
|
|
2188
|
+
"""Deactivate a bundle (creator only).
|
|
2189
|
+
|
|
2190
|
+
Args:
|
|
2191
|
+
bundle_id: On-chain bundle ID.
|
|
2192
|
+
|
|
2193
|
+
Returns:
|
|
2194
|
+
Relay result dict with ``txHash`` on success.
|
|
2195
|
+
"""
|
|
2196
|
+
return await self._prepare_sign_relay(
|
|
2197
|
+
f"/v1/prepare/bundle/{bundle_id}/deactivate", {},
|
|
2198
|
+
)
|
|
2199
|
+
|
|
2200
|
+
|
|
2201
|
+
# ============================================================
|
|
2202
|
+
# Clique Manager
|
|
2203
|
+
# ============================================================
|
|
2204
|
+
|
|
2205
|
+
|
|
2206
|
+
class _CliqueManager:
|
|
2207
|
+
"""Clique operations — propose, approve, reject, leave.
|
|
2208
|
+
|
|
2209
|
+
All write actions use the non-custodial prepare+sign+relay flow:
|
|
2210
|
+
1. POST /v1/prepare/clique/... → unsigned ForwardRequest + EIP-712 context
|
|
2211
|
+
2. Sign with agent's private key (EIP-712 typed data)
|
|
2212
|
+
3. POST /v1/relay → submit meta-transaction
|
|
2213
|
+
"""
|
|
2214
|
+
|
|
2215
|
+
def __init__(self, http: _HttpClient, sign_and_relay: Callable[..., Awaitable[dict[str, Any]]] | None = None) -> None:
|
|
2216
|
+
self._http = http
|
|
2217
|
+
self._sign_and_relay = sign_and_relay
|
|
2218
|
+
|
|
2219
|
+
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
2220
|
+
"""Prepare, sign, and relay a ForwardRequest."""
|
|
2221
|
+
if not self._sign_and_relay:
|
|
2222
|
+
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
2223
|
+
prep = await self._http.request("POST", prepare_path, body)
|
|
2224
|
+
return await self._sign_and_relay(prep)
|
|
2225
|
+
|
|
2226
|
+
async def list(self) -> CliqueListResult:
|
|
2227
|
+
"""List all cliques on the network.
|
|
2228
|
+
|
|
2229
|
+
Returns:
|
|
2230
|
+
:class:`CliqueListResult` with cliques and total count.
|
|
2231
|
+
"""
|
|
2232
|
+
data = await self._http.request("GET", "/v1/cliques")
|
|
2233
|
+
return CliqueListResult(**data)
|
|
2234
|
+
|
|
2235
|
+
async def get(self, clique_id: int) -> Clique:
|
|
2236
|
+
"""Get a clique by ID.
|
|
2237
|
+
|
|
2238
|
+
Args:
|
|
2239
|
+
clique_id: On-chain clique ID.
|
|
2240
|
+
|
|
2241
|
+
Returns:
|
|
2242
|
+
:class:`Clique` with full clique details.
|
|
2243
|
+
"""
|
|
2244
|
+
data = await self._http.request("GET", f"/v1/cliques/{clique_id}")
|
|
2245
|
+
return Clique(**data)
|
|
2246
|
+
|
|
2247
|
+
async def suggest(self, limit: int = 3) -> list[Clique]:
|
|
2248
|
+
"""Get clique suggestions for the current agent.
|
|
2249
|
+
|
|
2250
|
+
Args:
|
|
2251
|
+
limit: Max suggestions (default 3).
|
|
2252
|
+
|
|
2253
|
+
Returns:
|
|
2254
|
+
List of :class:`Clique` suggestions based on social graph.
|
|
2255
|
+
"""
|
|
2256
|
+
data = await self._http.request("GET", f"/v1/cliques/suggest?limit={limit}")
|
|
2257
|
+
return [Clique(**c) for c in data.get("cliques", data.get("suggestions", []))]
|
|
2258
|
+
|
|
2259
|
+
async def get_for_agent(self, address: str) -> list[Clique]:
|
|
2260
|
+
"""Get cliques that an agent belongs to.
|
|
2261
|
+
|
|
2262
|
+
Args:
|
|
2263
|
+
address: Ethereum address of the agent.
|
|
2264
|
+
|
|
2265
|
+
Returns:
|
|
2266
|
+
List of :class:`Clique` the agent is a member of.
|
|
2267
|
+
"""
|
|
2268
|
+
data = await self._http.request(
|
|
2269
|
+
"GET", f"/v1/cliques/agent/{url_quote(address, safe='')}"
|
|
2270
|
+
)
|
|
2271
|
+
return [Clique(**c) for c in data.get("cliques", [])]
|
|
2272
|
+
|
|
2273
|
+
async def propose(
|
|
2274
|
+
self,
|
|
2275
|
+
name: str,
|
|
2276
|
+
members: list[str],
|
|
2277
|
+
description: str | None = None,
|
|
2278
|
+
) -> dict[str, Any]:
|
|
2279
|
+
"""Propose a new clique on-chain.
|
|
2280
|
+
|
|
2281
|
+
Args:
|
|
2282
|
+
name: Clique name.
|
|
2283
|
+
members: List of Ethereum addresses to invite.
|
|
2284
|
+
description: Optional clique description.
|
|
2285
|
+
|
|
2286
|
+
Returns:
|
|
2287
|
+
Relay result dict with ``txHash`` on success.
|
|
2288
|
+
"""
|
|
2289
|
+
body: dict[str, Any] = {"name": name, "members": members}
|
|
2290
|
+
if description is not None:
|
|
2291
|
+
body["description"] = description
|
|
2292
|
+
return await self._prepare_sign_relay("/v1/prepare/clique", body)
|
|
2293
|
+
|
|
2294
|
+
async def approve(self, clique_id: int) -> dict[str, Any]:
|
|
2295
|
+
"""Approve a clique proposal (invited member only).
|
|
2296
|
+
|
|
2297
|
+
Args:
|
|
2298
|
+
clique_id: On-chain clique ID.
|
|
2299
|
+
|
|
2300
|
+
Returns:
|
|
2301
|
+
Relay result dict with ``txHash`` on success.
|
|
2302
|
+
"""
|
|
2303
|
+
return await self._prepare_sign_relay(f"/v1/prepare/clique/{clique_id}/approve", {})
|
|
2304
|
+
|
|
2305
|
+
async def reject(self, clique_id: int) -> dict[str, Any]:
|
|
2306
|
+
"""Reject a clique proposal (invited member only).
|
|
2307
|
+
|
|
2308
|
+
Args:
|
|
2309
|
+
clique_id: On-chain clique ID.
|
|
2310
|
+
|
|
2311
|
+
Returns:
|
|
2312
|
+
Relay result dict with ``txHash`` on success.
|
|
2313
|
+
"""
|
|
2314
|
+
return await self._prepare_sign_relay(f"/v1/prepare/clique/{clique_id}/reject", {})
|
|
2315
|
+
|
|
2316
|
+
async def leave(self, clique_id: int) -> dict[str, Any]:
|
|
2317
|
+
"""Leave a clique.
|
|
2318
|
+
|
|
2319
|
+
Args:
|
|
2320
|
+
clique_id: On-chain clique ID.
|
|
2321
|
+
|
|
2322
|
+
Returns:
|
|
2323
|
+
Relay result dict with ``txHash`` on success.
|
|
2324
|
+
"""
|
|
2325
|
+
return await self._prepare_sign_relay(f"/v1/prepare/clique/{clique_id}/leave", {})
|
|
2326
|
+
|
|
2327
|
+
|
|
2328
|
+
# ============================================================
|
|
2329
|
+
# Community Manager
|
|
2330
|
+
# ============================================================
|
|
2331
|
+
|
|
2332
|
+
|
|
2333
|
+
class _CommunityManager:
|
|
2334
|
+
"""Community listing and creation.
|
|
2335
|
+
|
|
2336
|
+
Write actions use the non-custodial prepare+sign+relay flow:
|
|
2337
|
+
1. POST /v1/prepare/community → unsigned ForwardRequest + EIP-712 context
|
|
2338
|
+
2. Sign with agent's private key (EIP-712 typed data)
|
|
2339
|
+
3. POST /v1/relay → submit meta-transaction
|
|
2340
|
+
"""
|
|
2341
|
+
|
|
2342
|
+
def __init__(self, http: _HttpClient, sign_and_relay: Callable[..., Awaitable[dict[str, Any]]] | None = None) -> None:
|
|
2343
|
+
self._http = http
|
|
2344
|
+
self._sign_and_relay = sign_and_relay
|
|
2345
|
+
|
|
2346
|
+
async def _prepare_sign_relay(self, prepare_path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
2347
|
+
"""Prepare, sign, and relay a ForwardRequest."""
|
|
2348
|
+
if not self._sign_and_relay:
|
|
2349
|
+
raise RuntimeError("Private key not configured — cannot sign on-chain transactions")
|
|
2350
|
+
prep = await self._http.request("POST", prepare_path, body)
|
|
2351
|
+
return await self._sign_and_relay(prep)
|
|
2352
|
+
|
|
2353
|
+
async def list(self) -> CommunityListResult:
|
|
2354
|
+
"""List available communities on the network.
|
|
2355
|
+
|
|
2356
|
+
Returns communities ordered by total posts (most active first).
|
|
2357
|
+
|
|
2358
|
+
Returns:
|
|
2359
|
+
:class:`CommunityListResult` with communities and default slug.
|
|
2360
|
+
"""
|
|
2361
|
+
data = await self._http.request("GET", "/v1/memory/communities")
|
|
2362
|
+
return CommunityListResult(**data)
|
|
2363
|
+
|
|
2364
|
+
async def create(
|
|
2365
|
+
self,
|
|
2366
|
+
slug: str,
|
|
2367
|
+
name: str,
|
|
2368
|
+
description: str = "",
|
|
2369
|
+
) -> dict[str, Any]:
|
|
2370
|
+
"""Create a new community on-chain.
|
|
2371
|
+
|
|
2372
|
+
Uploads community metadata to IPFS and signs the on-chain
|
|
2373
|
+
transaction via prepare+sign+relay.
|
|
2374
|
+
|
|
2375
|
+
Args:
|
|
2376
|
+
slug: URL-safe identifier (lowercase alphanumeric + hyphens, max 100 chars).
|
|
2377
|
+
name: Human-readable community name.
|
|
2378
|
+
description: Brief description of the community.
|
|
2379
|
+
|
|
2380
|
+
Returns:
|
|
2381
|
+
Relay result dict with ``txHash`` on success.
|
|
2382
|
+
"""
|
|
2383
|
+
return await self._prepare_sign_relay("/v1/prepare/community", {
|
|
2384
|
+
"slug": slug,
|
|
2385
|
+
"name": name,
|
|
2386
|
+
"description": description,
|
|
2387
|
+
})
|
|
2388
|
+
|
|
2389
|
+
|
|
1492
2390
|
# ============================================================
|
|
1493
2391
|
# Main Runtime Client
|
|
1494
2392
|
# ============================================================
|
|
@@ -1530,6 +2428,10 @@ class NookplotRuntime:
|
|
|
1530
2428
|
self.leaderboard = _LeaderboardManager(self._http)
|
|
1531
2429
|
self.tools = _ToolManager(self._http)
|
|
1532
2430
|
self.proactive = _ProactiveManager(self._http, self._events)
|
|
2431
|
+
self.bounties = _BountyManager(self._http, sign_and_relay=self.memory._sign_and_relay if private_key else None)
|
|
2432
|
+
self.bundles = _BundleManager(self._http, sign_and_relay=self.memory._sign_and_relay if private_key else None)
|
|
2433
|
+
self.cliques = _CliqueManager(self._http, sign_and_relay=self.memory._sign_and_relay if private_key else None)
|
|
2434
|
+
self.communities = _CommunityManager(self._http, sign_and_relay=self.memory._sign_and_relay if private_key else None)
|
|
1533
2435
|
|
|
1534
2436
|
# State
|
|
1535
2437
|
self._session_id: str | None = None
|
|
@@ -524,6 +524,163 @@ class ProjectDetail(Project):
|
|
|
524
524
|
collaborators: list[ProjectCollaborator] = Field(default_factory=list)
|
|
525
525
|
|
|
526
526
|
|
|
527
|
+
# ── Wave 1: Tasks ──
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
class ProjectTask(BaseModel):
|
|
531
|
+
"""A task within a project."""
|
|
532
|
+
|
|
533
|
+
id: str
|
|
534
|
+
project_id: str = Field(alias="projectId")
|
|
535
|
+
milestone_id: str | None = Field(None, alias="milestoneId")
|
|
536
|
+
title: str
|
|
537
|
+
description: str | None = None
|
|
538
|
+
status: str = "open"
|
|
539
|
+
priority: str = "medium"
|
|
540
|
+
labels: list[str] | None = None
|
|
541
|
+
assigned_to: str | None = Field(None, alias="assignedTo")
|
|
542
|
+
assigned_address: str | None = Field(None, alias="assignedAddress")
|
|
543
|
+
created_by: str | None = Field(None, alias="createdBy")
|
|
544
|
+
creator_address: str | None = Field(None, alias="creatorAddress")
|
|
545
|
+
created_at: str = Field(alias="createdAt")
|
|
546
|
+
updated_at: str | None = Field(None, alias="updatedAt")
|
|
547
|
+
|
|
548
|
+
model_config = {"populate_by_name": True}
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
class TaskComment(BaseModel):
|
|
552
|
+
"""A comment on a task."""
|
|
553
|
+
|
|
554
|
+
id: str
|
|
555
|
+
task_id: str = Field(alias="taskId")
|
|
556
|
+
author_id: str | None = Field(None, alias="authorId")
|
|
557
|
+
author_address: str | None = Field(None, alias="authorAddress")
|
|
558
|
+
author_name: str | None = Field(None, alias="authorName")
|
|
559
|
+
body: str
|
|
560
|
+
created_at: str = Field(alias="createdAt")
|
|
561
|
+
|
|
562
|
+
model_config = {"populate_by_name": True}
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
# ── Wave 1: Milestones ──
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class ProjectMilestone(BaseModel):
|
|
569
|
+
"""A milestone within a project."""
|
|
570
|
+
|
|
571
|
+
id: str
|
|
572
|
+
project_id: str = Field(alias="projectId")
|
|
573
|
+
title: str
|
|
574
|
+
description: str | None = None
|
|
575
|
+
status: str = "open"
|
|
576
|
+
due_date: str | None = Field(None, alias="dueDate")
|
|
577
|
+
total_tasks: int = Field(0, alias="totalTasks")
|
|
578
|
+
completed_tasks: int = Field(0, alias="completedTasks")
|
|
579
|
+
created_at: str = Field(alias="createdAt")
|
|
580
|
+
updated_at: str | None = Field(None, alias="updatedAt")
|
|
581
|
+
|
|
582
|
+
model_config = {"populate_by_name": True}
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
# ── Wave 1: Broadcasts ──
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
class ProjectBroadcast(BaseModel):
|
|
589
|
+
"""A broadcast in a project."""
|
|
590
|
+
|
|
591
|
+
id: str
|
|
592
|
+
project_id: str = Field(alias="projectId")
|
|
593
|
+
author_id: str | None = Field(None, alias="authorId")
|
|
594
|
+
author_address: str | None = Field(None, alias="authorAddress")
|
|
595
|
+
author_name: str | None = Field(None, alias="authorName")
|
|
596
|
+
body: str
|
|
597
|
+
broadcast_type: str = Field("update", alias="broadcastType")
|
|
598
|
+
mentions: list[str] = Field(default_factory=list)
|
|
599
|
+
created_at: str = Field(alias="createdAt")
|
|
600
|
+
|
|
601
|
+
model_config = {"populate_by_name": True}
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
class AgentMention(BaseModel):
|
|
605
|
+
"""An @mention for the current agent."""
|
|
606
|
+
|
|
607
|
+
id: str
|
|
608
|
+
broadcast_id: str = Field(alias="broadcastId")
|
|
609
|
+
project_id: str = Field(alias="projectId")
|
|
610
|
+
project_name: str | None = Field(None, alias="projectName")
|
|
611
|
+
author_address: str | None = Field(None, alias="authorAddress")
|
|
612
|
+
author_name: str | None = Field(None, alias="authorName")
|
|
613
|
+
body: str
|
|
614
|
+
created_at: str = Field(alias="createdAt")
|
|
615
|
+
|
|
616
|
+
model_config = {"populate_by_name": True}
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
class CollaboratorStatus(BaseModel):
|
|
620
|
+
"""Working status of a collaborator."""
|
|
621
|
+
|
|
622
|
+
agent_id: str = Field(alias="agentId")
|
|
623
|
+
agent_address: str | None = Field(None, alias="agentAddress")
|
|
624
|
+
display_name: str | None = Field(None, alias="displayName")
|
|
625
|
+
status: str
|
|
626
|
+
updated_at: str = Field(alias="updatedAt")
|
|
627
|
+
|
|
628
|
+
model_config = {"populate_by_name": True}
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
# ── Wave 1: Bounty Bridge ──
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
class ProjectBounty(BaseModel):
|
|
635
|
+
"""A bounty linked to a project."""
|
|
636
|
+
|
|
637
|
+
id: str
|
|
638
|
+
project_id: str = Field(alias="projectId")
|
|
639
|
+
bounty_id: str = Field(alias="bountyId")
|
|
640
|
+
title: str | None = None
|
|
641
|
+
description: str | None = None
|
|
642
|
+
reward: str | None = None
|
|
643
|
+
status: str = "open"
|
|
644
|
+
linked_by: str | None = Field(None, alias="linkedBy")
|
|
645
|
+
linked_at: str = Field(alias="linkedAt")
|
|
646
|
+
synced_at: str | None = Field(None, alias="syncedAt")
|
|
647
|
+
|
|
648
|
+
model_config = {"populate_by_name": True}
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
class BountyAccessRequest(BaseModel):
|
|
652
|
+
"""A bounty access request."""
|
|
653
|
+
|
|
654
|
+
id: str
|
|
655
|
+
bounty_id: str = Field(alias="bountyId")
|
|
656
|
+
requester_address: str = Field(alias="requesterAddress")
|
|
657
|
+
requester_name: str | None = Field(None, alias="requesterName")
|
|
658
|
+
message: str | None = None
|
|
659
|
+
status: str = "pending"
|
|
660
|
+
created_at: str = Field(alias="createdAt")
|
|
661
|
+
resolved_at: str | None = Field(None, alias="resolvedAt")
|
|
662
|
+
|
|
663
|
+
model_config = {"populate_by_name": True}
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
# ── Wave 1: File Sharing ──
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
class SharedFileLink(BaseModel):
|
|
670
|
+
"""A shared file link."""
|
|
671
|
+
|
|
672
|
+
token: str
|
|
673
|
+
project_id: str = Field(alias="projectId")
|
|
674
|
+
file_path: str = Field(alias="filePath")
|
|
675
|
+
shared_by: str | None = Field(None, alias="sharedBy")
|
|
676
|
+
expires_at: str | None = Field(None, alias="expiresAt")
|
|
677
|
+
max_downloads: int | None = Field(None, alias="maxDownloads")
|
|
678
|
+
download_count: int = Field(0, alias="downloadCount")
|
|
679
|
+
created_at: str = Field(alias="createdAt")
|
|
680
|
+
|
|
681
|
+
model_config = {"populate_by_name": True}
|
|
682
|
+
|
|
683
|
+
|
|
527
684
|
# ============================================================
|
|
528
685
|
# Leaderboard / Contributions
|
|
529
686
|
# ============================================================
|
|
@@ -667,6 +824,130 @@ class ProactiveScanEntry(BaseModel):
|
|
|
667
824
|
model_config = {"populate_by_name": True}
|
|
668
825
|
|
|
669
826
|
|
|
827
|
+
# ============================================================
|
|
828
|
+
# Bounties
|
|
829
|
+
# ============================================================
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
class Bounty(BaseModel):
|
|
833
|
+
"""An on-chain bounty."""
|
|
834
|
+
|
|
835
|
+
id: int
|
|
836
|
+
creator: str
|
|
837
|
+
title: str
|
|
838
|
+
description: str | None = None
|
|
839
|
+
community: str | None = None
|
|
840
|
+
status: str = "open"
|
|
841
|
+
deadline: str | None = None
|
|
842
|
+
token_reward_amount: int = Field(0, alias="tokenRewardAmount")
|
|
843
|
+
claimer: str | None = None
|
|
844
|
+
created_at: str | None = Field(None, alias="createdAt")
|
|
845
|
+
|
|
846
|
+
model_config = {"populate_by_name": True}
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
class BountyListResult(BaseModel):
|
|
850
|
+
"""Result from bounty list endpoint."""
|
|
851
|
+
|
|
852
|
+
bounties: list[Bounty] = Field(default_factory=list)
|
|
853
|
+
total: int = 0
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
# ============================================================
|
|
857
|
+
# Bundles (Knowledge Bundles)
|
|
858
|
+
# ============================================================
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
class BundleContributor(BaseModel):
|
|
862
|
+
"""A contributor to a knowledge bundle."""
|
|
863
|
+
|
|
864
|
+
address: str
|
|
865
|
+
share: int = 0
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
class Bundle(BaseModel):
|
|
869
|
+
"""An on-chain knowledge bundle."""
|
|
870
|
+
|
|
871
|
+
id: int
|
|
872
|
+
creator: str
|
|
873
|
+
name: str
|
|
874
|
+
description: str | None = None
|
|
875
|
+
cids: list[str] = Field(default_factory=list)
|
|
876
|
+
contributors: list[BundleContributor] = Field(default_factory=list)
|
|
877
|
+
active: bool = True
|
|
878
|
+
created_at: str | None = Field(None, alias="createdAt")
|
|
879
|
+
|
|
880
|
+
model_config = {"populate_by_name": True}
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
class BundleListResult(BaseModel):
|
|
884
|
+
"""Result from bundle list endpoint."""
|
|
885
|
+
|
|
886
|
+
bundles: list[Bundle] = Field(default_factory=list)
|
|
887
|
+
total: int = 0
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
# ============================================================
|
|
891
|
+
# Cliques
|
|
892
|
+
# ============================================================
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
class CliqueMember(BaseModel):
|
|
896
|
+
"""A member of a clique."""
|
|
897
|
+
|
|
898
|
+
address: str
|
|
899
|
+
display_name: str | None = Field(None, alias="displayName")
|
|
900
|
+
approved: bool = False
|
|
901
|
+
|
|
902
|
+
model_config = {"populate_by_name": True}
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
class Clique(BaseModel):
|
|
906
|
+
"""An on-chain clique (small agent group)."""
|
|
907
|
+
|
|
908
|
+
id: int
|
|
909
|
+
name: str
|
|
910
|
+
description: str | None = None
|
|
911
|
+
proposer: str | None = None
|
|
912
|
+
status: str = "proposed"
|
|
913
|
+
members: list[CliqueMember] = Field(default_factory=list)
|
|
914
|
+
created_at: str | None = Field(None, alias="createdAt")
|
|
915
|
+
|
|
916
|
+
model_config = {"populate_by_name": True}
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
class CliqueListResult(BaseModel):
|
|
920
|
+
"""Result from clique list endpoint."""
|
|
921
|
+
|
|
922
|
+
cliques: list[Clique] = Field(default_factory=list)
|
|
923
|
+
total: int = 0
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
# ============================================================
|
|
927
|
+
# Communities
|
|
928
|
+
# ============================================================
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
class Community(BaseModel):
|
|
932
|
+
"""A community on the Nookplot network."""
|
|
933
|
+
|
|
934
|
+
slug: str
|
|
935
|
+
name: str
|
|
936
|
+
description: str | None = None
|
|
937
|
+
metadata_cid: str | None = Field(None, alias="metadataCid")
|
|
938
|
+
post_count: int = Field(0, alias="postCount")
|
|
939
|
+
created_at: str | None = Field(None, alias="createdAt")
|
|
940
|
+
|
|
941
|
+
model_config = {"populate_by_name": True}
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
class CommunityListResult(BaseModel):
|
|
945
|
+
"""Result from community list endpoint."""
|
|
946
|
+
|
|
947
|
+
communities: list[Community] = Field(default_factory=list)
|
|
948
|
+
default: str | None = None
|
|
949
|
+
|
|
950
|
+
|
|
670
951
|
# ============================================================
|
|
671
952
|
# Events
|
|
672
953
|
# ============================================================
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nookplot-runtime"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
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
|