nookplot-runtime 0.5.6__tar.gz → 0.5.9__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.6 → nookplot_runtime-0.5.9}/.gitignore +12 -10
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/PKG-INFO +1 -1
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/nookplot_runtime/__init__.py +9 -1
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/nookplot_runtime/autonomous.py +93 -0
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/nookplot_runtime/client.py +168 -6
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/nookplot_runtime/types.py +96 -5
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/pyproject.toml +1 -1
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/README.md +0 -0
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/nookplot_runtime/content_safety.py +0 -0
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/nookplot_runtime/events.py +0 -0
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/requirements.lock +0 -0
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/tests/__init__.py +0 -0
- {nookplot_runtime-0.5.6 → nookplot_runtime-0.5.9}/tests/test_client.py +0 -0
|
@@ -12,20 +12,22 @@ subgraph/generated/
|
|
|
12
12
|
# Environment variables (SECRETS — never commit)
|
|
13
13
|
.env
|
|
14
14
|
|
|
15
|
-
# Test
|
|
16
|
-
scripts
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.test
|
|
20
|
-
.
|
|
21
|
-
scripts/.agent-b-cli.log
|
|
22
|
-
.seed-agents.json
|
|
23
|
-
.seed-agents-wave2.json
|
|
15
|
+
# Test/seed scripts (contain API keys, private keys, agent credentials)
|
|
16
|
+
scripts/
|
|
17
|
+
|
|
18
|
+
# Agent state files (credentials, key material — never commit)
|
|
19
|
+
.test-*-agents.json
|
|
20
|
+
.seed-agents*.json
|
|
24
21
|
.swarm-agents.json
|
|
25
22
|
.organic-activity-state.json
|
|
26
23
|
.storyline-agents.json
|
|
27
24
|
.storyline-v2-agents.json
|
|
28
|
-
.
|
|
25
|
+
.wave*-storyline-agents.json
|
|
26
|
+
.populate-content-state.json
|
|
27
|
+
.test-callback-agents*.json
|
|
28
|
+
|
|
29
|
+
# Log files from populate/seed runs
|
|
30
|
+
*.log
|
|
29
31
|
|
|
30
32
|
# Python
|
|
31
33
|
__pycache__/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nookplot-runtime
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.9
|
|
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
|
|
@@ -71,6 +71,10 @@ from nookplot_runtime.types import (
|
|
|
71
71
|
CliqueListResult,
|
|
72
72
|
Community,
|
|
73
73
|
CommunityListResult,
|
|
74
|
+
MatchedSkill,
|
|
75
|
+
MatchResult,
|
|
76
|
+
TeamMember,
|
|
77
|
+
TeamResult,
|
|
74
78
|
)
|
|
75
79
|
|
|
76
80
|
__all__ = [
|
|
@@ -106,6 +110,10 @@ __all__ = [
|
|
|
106
110
|
"CliqueListResult",
|
|
107
111
|
"Community",
|
|
108
112
|
"CommunityListResult",
|
|
113
|
+
"MatchedSkill",
|
|
114
|
+
"MatchResult",
|
|
115
|
+
"TeamMember",
|
|
116
|
+
"TeamResult",
|
|
109
117
|
"sanitize_for_prompt",
|
|
110
118
|
"wrap_untrusted",
|
|
111
119
|
"assess_threat_level",
|
|
@@ -113,4 +121,4 @@ __all__ = [
|
|
|
113
121
|
"UNTRUSTED_CONTENT_INSTRUCTION",
|
|
114
122
|
]
|
|
115
123
|
|
|
116
|
-
__version__ = "0.2.
|
|
124
|
+
__version__ = "0.2.14"
|
|
@@ -1435,6 +1435,7 @@ class AutonomousAgent:
|
|
|
1435
1435
|
_ON_CHAIN_ACTIONS = {
|
|
1436
1436
|
"vote", "follow_agent", "attest_agent", "create_community",
|
|
1437
1437
|
"create_project", "propose_clique", "propose_guild", "claim_bounty",
|
|
1438
|
+
"deploy_preview",
|
|
1438
1439
|
}
|
|
1439
1440
|
if action_type in _ON_CHAIN_ACTIONS:
|
|
1440
1441
|
approved = await self._request_approval(action_type, payload, suggested_content, action_id)
|
|
@@ -1581,6 +1582,24 @@ class AutonomousAgent:
|
|
|
1581
1582
|
review_result = await self._runtime.projects.submit_review(pid, cid, verdict, body)
|
|
1582
1583
|
result = review_result if isinstance(review_result, dict) else {"verdict": verdict}
|
|
1583
1584
|
|
|
1585
|
+
elif action_type == "request_ai_review":
|
|
1586
|
+
# AI-powered code review — costs 150 credits (1.50 cr)
|
|
1587
|
+
pid = payload.get("projectId")
|
|
1588
|
+
cid = payload.get("commitId")
|
|
1589
|
+
if not pid or not cid:
|
|
1590
|
+
raise ValueError("request_ai_review requires projectId and commitId")
|
|
1591
|
+
ai_result = await self._runtime.projects.request_ai_review(pid, cid)
|
|
1592
|
+
result = (
|
|
1593
|
+
ai_result
|
|
1594
|
+
if isinstance(ai_result, dict)
|
|
1595
|
+
else {
|
|
1596
|
+
"reviewId": getattr(ai_result, "review_id", None),
|
|
1597
|
+
"verdict": getattr(ai_result, "verdict", None),
|
|
1598
|
+
"findingsCount": getattr(ai_result, "findings_count", 0),
|
|
1599
|
+
"creditsCost": getattr(ai_result, "credits_cost", 0),
|
|
1600
|
+
}
|
|
1601
|
+
)
|
|
1602
|
+
|
|
1584
1603
|
elif action_type == "gateway_commit":
|
|
1585
1604
|
pid = payload.get("projectId")
|
|
1586
1605
|
files = payload.get("files")
|
|
@@ -1620,6 +1639,80 @@ class AutonomousAgent:
|
|
|
1620
1639
|
await self._runtime.inbox.send(to=addr, content=message)
|
|
1621
1640
|
result = {"sent": True, "to": addr}
|
|
1622
1641
|
|
|
1642
|
+
elif action_type == "deploy_preview":
|
|
1643
|
+
proj_id = payload.get("projectId")
|
|
1644
|
+
if not proj_id:
|
|
1645
|
+
raise ValueError("deploy_preview requires projectId")
|
|
1646
|
+
prepaid_hours = payload.get("prepaidHours", 2)
|
|
1647
|
+
prep = await self._runtime._http.request(
|
|
1648
|
+
"POST", f"/v1/prepare/project/{proj_id}/deployment",
|
|
1649
|
+
{"prepaidHours": prepaid_hours},
|
|
1650
|
+
)
|
|
1651
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
1652
|
+
tx_hash = relay.get("txHash") if isinstance(relay, dict) else None
|
|
1653
|
+
result = {"txHash": tx_hash, "projectId": proj_id}
|
|
1654
|
+
|
|
1655
|
+
elif action_type == "create_task":
|
|
1656
|
+
proj_id = payload.get("projectId")
|
|
1657
|
+
title = suggested_content or payload.get("title")
|
|
1658
|
+
if not proj_id or not title:
|
|
1659
|
+
raise ValueError("create_task requires projectId and title")
|
|
1660
|
+
task_result = await self._runtime.projects.create_task(
|
|
1661
|
+
proj_id, title,
|
|
1662
|
+
description=payload.get("description"),
|
|
1663
|
+
milestone_id=payload.get("milestoneId"),
|
|
1664
|
+
priority=payload.get("priority", "medium"),
|
|
1665
|
+
labels=payload.get("labels"),
|
|
1666
|
+
)
|
|
1667
|
+
result = task_result if isinstance(task_result, dict) else {"created": True}
|
|
1668
|
+
|
|
1669
|
+
elif action_type in ("complete_task", "update_task"):
|
|
1670
|
+
proj_id = payload.get("projectId")
|
|
1671
|
+
tid = payload.get("taskId")
|
|
1672
|
+
if not proj_id or not tid:
|
|
1673
|
+
raise ValueError(f"{action_type} requires projectId and taskId")
|
|
1674
|
+
kw: dict[str, Any] = {}
|
|
1675
|
+
if action_type == "complete_task":
|
|
1676
|
+
kw["status"] = "completed"
|
|
1677
|
+
else:
|
|
1678
|
+
if payload.get("status"):
|
|
1679
|
+
kw["status"] = payload["status"]
|
|
1680
|
+
if payload.get("title"):
|
|
1681
|
+
kw["title"] = payload["title"]
|
|
1682
|
+
if payload.get("description"):
|
|
1683
|
+
kw["description"] = payload["description"]
|
|
1684
|
+
if payload.get("priority"):
|
|
1685
|
+
kw["priority"] = payload["priority"]
|
|
1686
|
+
if payload.get("milestoneId") is not None:
|
|
1687
|
+
kw["milestone_id"] = payload["milestoneId"]
|
|
1688
|
+
if payload.get("labels"):
|
|
1689
|
+
kw["labels"] = payload["labels"]
|
|
1690
|
+
task_result = await self._runtime.projects.update_task(proj_id, tid, **kw)
|
|
1691
|
+
result = task_result if isinstance(task_result, dict) else {"updated": True}
|
|
1692
|
+
|
|
1693
|
+
elif action_type == "find_matching_agents":
|
|
1694
|
+
skills = payload.get("skills", [])
|
|
1695
|
+
if not skills:
|
|
1696
|
+
raise ValueError("find_matching_agents requires skills array")
|
|
1697
|
+
match_result = await self._runtime.matching.find_agents(
|
|
1698
|
+
skills,
|
|
1699
|
+
count=payload.get("count"),
|
|
1700
|
+
available_only=payload.get("availableOnly"),
|
|
1701
|
+
)
|
|
1702
|
+
result = match_result if isinstance(match_result, dict) else {"matches": []}
|
|
1703
|
+
|
|
1704
|
+
elif action_type == "assemble_team":
|
|
1705
|
+
description = suggested_content or payload.get("description", "")
|
|
1706
|
+
if not description:
|
|
1707
|
+
raise ValueError("assemble_team requires description")
|
|
1708
|
+
team_result = await self._runtime.matching.assemble_team(
|
|
1709
|
+
description,
|
|
1710
|
+
required_skills=payload.get("requiredSkills"),
|
|
1711
|
+
team_size=payload.get("teamSize"),
|
|
1712
|
+
filters=payload.get("filters"),
|
|
1713
|
+
)
|
|
1714
|
+
result = team_result if isinstance(team_result, dict) else {"assembled": True}
|
|
1715
|
+
|
|
1623
1716
|
else:
|
|
1624
1717
|
self._broadcast("action_skipped", f"⏭ Unknown action: {action_type}", {
|
|
1625
1718
|
"action": action_type, "actionId": action_id,
|
|
@@ -21,6 +21,7 @@ Usage::
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
23
|
import asyncio
|
|
24
|
+
import importlib.metadata
|
|
24
25
|
import json
|
|
25
26
|
import logging
|
|
26
27
|
from typing import Any, Awaitable, Callable
|
|
@@ -28,6 +29,11 @@ from urllib.parse import quote as url_quote
|
|
|
28
29
|
|
|
29
30
|
import httpx
|
|
30
31
|
|
|
32
|
+
try:
|
|
33
|
+
_CLIENT_VERSION = importlib.metadata.version("nookplot-runtime")
|
|
34
|
+
except importlib.metadata.PackageNotFoundError:
|
|
35
|
+
_CLIENT_VERSION = "0.0.0"
|
|
36
|
+
|
|
31
37
|
from nookplot_runtime.events import EventManager, EventHandler
|
|
32
38
|
from nookplot_runtime.types import (
|
|
33
39
|
ConnectResult,
|
|
@@ -60,6 +66,7 @@ from nookplot_runtime.types import (
|
|
|
60
66
|
FileCommit,
|
|
61
67
|
FileCommitDetail,
|
|
62
68
|
CommitReview,
|
|
69
|
+
AIReviewResult,
|
|
63
70
|
ProjectActivityEvent,
|
|
64
71
|
LeaderboardEntry,
|
|
65
72
|
ContributionScore,
|
|
@@ -581,8 +588,14 @@ class _EconomyManager:
|
|
|
581
588
|
|
|
582
589
|
async def get_balance(self) -> BalanceInfo:
|
|
583
590
|
data = await self._http.request("GET", "/v1/credits/balance")
|
|
584
|
-
#
|
|
585
|
-
credits_data =
|
|
591
|
+
# Map gateway response to CreditBalance fields
|
|
592
|
+
credits_data = {
|
|
593
|
+
"available": data.get("balance", 0),
|
|
594
|
+
"spent": data.get("lifetimeSpent", 0),
|
|
595
|
+
"dailySpent": 0,
|
|
596
|
+
"dailyLimit": 0,
|
|
597
|
+
"availableStored": data.get("balanceStored", 0),
|
|
598
|
+
}
|
|
586
599
|
try:
|
|
587
600
|
revenue_data = await self._http.request("GET", "/v1/revenue/balance")
|
|
588
601
|
except Exception:
|
|
@@ -657,11 +670,11 @@ class _EconomyManager:
|
|
|
657
670
|
low_threshold: int | None = None,
|
|
658
671
|
critical_threshold: int | None = None,
|
|
659
672
|
) -> dict[str, Any]:
|
|
660
|
-
"""Set budget alert thresholds (in
|
|
673
|
+
"""Set budget alert thresholds (in credits).
|
|
661
674
|
|
|
662
675
|
Args:
|
|
663
|
-
low_threshold: Low budget threshold (
|
|
664
|
-
critical_threshold: Critical budget threshold (
|
|
676
|
+
low_threshold: Low budget threshold (credits).
|
|
677
|
+
critical_threshold: Critical budget threshold (credits).
|
|
665
678
|
|
|
666
679
|
Returns:
|
|
667
680
|
Dict with ``success`` boolean.
|
|
@@ -1261,6 +1274,25 @@ class _ProjectManager:
|
|
|
1261
1274
|
)
|
|
1262
1275
|
return [CommitReview(**r) for r in data.get("reviews", [])]
|
|
1263
1276
|
|
|
1277
|
+
async def request_ai_review(
|
|
1278
|
+
self, project_id: str, commit_id: str
|
|
1279
|
+
) -> AIReviewResult:
|
|
1280
|
+
"""Request an AI-powered code review on a commit.
|
|
1281
|
+
|
|
1282
|
+
**Costs 150 credits (1.50 cr).** Requires the project to have a linked
|
|
1283
|
+
GitHub repository (via GitHub export). The review is performed by Greptile
|
|
1284
|
+
and returns a verdict with detailed findings.
|
|
1285
|
+
|
|
1286
|
+
Args:
|
|
1287
|
+
project_id: Project containing the commit.
|
|
1288
|
+
commit_id: Commit to review.
|
|
1289
|
+
"""
|
|
1290
|
+
data = await self._http.request(
|
|
1291
|
+
"POST",
|
|
1292
|
+
f"/v1/projects/{url_quote(project_id, safe='')}/commits/{url_quote(commit_id, safe='')}/ai-review",
|
|
1293
|
+
)
|
|
1294
|
+
return AIReviewResult(**data)
|
|
1295
|
+
|
|
1264
1296
|
async def get_activity(
|
|
1265
1297
|
self, project_id: str, limit: int = 20
|
|
1266
1298
|
) -> list[ProjectActivityEvent]:
|
|
@@ -3086,6 +3118,125 @@ class _TeachingManager:
|
|
|
3086
3118
|
})
|
|
3087
3119
|
|
|
3088
3120
|
|
|
3121
|
+
class _MatchingManager:
|
|
3122
|
+
"""Skill-based agent matching and team assembly.
|
|
3123
|
+
|
|
3124
|
+
All operations are off-chain REST calls — no on-chain signing needed.
|
|
3125
|
+
"""
|
|
3126
|
+
|
|
3127
|
+
def __init__(self, http: _HttpClient) -> None:
|
|
3128
|
+
self._http = http
|
|
3129
|
+
|
|
3130
|
+
async def find_agents(
|
|
3131
|
+
self,
|
|
3132
|
+
skills: list[str],
|
|
3133
|
+
*,
|
|
3134
|
+
count: int | None = None,
|
|
3135
|
+
available_only: bool | None = None,
|
|
3136
|
+
min_reputation: int | None = None,
|
|
3137
|
+
exclude_addresses: list[str] | None = None,
|
|
3138
|
+
) -> dict[str, Any]:
|
|
3139
|
+
"""Find agents whose expertise tags match the requested skills.
|
|
3140
|
+
|
|
3141
|
+
Results are ranked by a composite score of skill match, contribution
|
|
3142
|
+
score, availability, collaboration history, and recency.
|
|
3143
|
+
|
|
3144
|
+
Args:
|
|
3145
|
+
skills: List of skill tags to search for (max 10).
|
|
3146
|
+
count: Maximum number of results (1–50, default 10).
|
|
3147
|
+
available_only: Only include agents accepting work.
|
|
3148
|
+
min_reputation: Minimum contribution score threshold.
|
|
3149
|
+
exclude_addresses: Addresses to exclude from results.
|
|
3150
|
+
|
|
3151
|
+
Returns:
|
|
3152
|
+
Dict with ``matches`` list and ``total`` count.
|
|
3153
|
+
"""
|
|
3154
|
+
params = f"?skills={url_quote(','.join(skills), safe=',')}"
|
|
3155
|
+
if count is not None:
|
|
3156
|
+
params += f"&count={count}"
|
|
3157
|
+
if available_only:
|
|
3158
|
+
params += "&available_only=true"
|
|
3159
|
+
if min_reputation is not None:
|
|
3160
|
+
params += f"&min_reputation={min_reputation}"
|
|
3161
|
+
return await self._http.request("GET", f"/v1/match{params}")
|
|
3162
|
+
|
|
3163
|
+
async def search_skills(
|
|
3164
|
+
self,
|
|
3165
|
+
query: str,
|
|
3166
|
+
limit: int = 20,
|
|
3167
|
+
) -> dict[str, Any]:
|
|
3168
|
+
"""Search known expertise tags by substring.
|
|
3169
|
+
|
|
3170
|
+
Args:
|
|
3171
|
+
query: Search substring (min 2 chars).
|
|
3172
|
+
limit: Max results (1–50, default 20).
|
|
3173
|
+
|
|
3174
|
+
Returns:
|
|
3175
|
+
Dict with ``skills`` list and ``query`` string.
|
|
3176
|
+
"""
|
|
3177
|
+
params = f"?q={url_quote(query, safe='')}&limit={limit}"
|
|
3178
|
+
return await self._http.request("GET", f"/v1/skills/search{params}")
|
|
3179
|
+
|
|
3180
|
+
async def assemble_team(
|
|
3181
|
+
self,
|
|
3182
|
+
description: str,
|
|
3183
|
+
*,
|
|
3184
|
+
required_skills: list[str] | None = None,
|
|
3185
|
+
team_size: int | None = None,
|
|
3186
|
+
filters: dict[str, Any] | None = None,
|
|
3187
|
+
) -> dict[str, Any]:
|
|
3188
|
+
"""Assemble an optimal team for a task.
|
|
3189
|
+
|
|
3190
|
+
Uses greedy set-cover to find agents that best cover the required
|
|
3191
|
+
skills. Rate limited to 10 requests per hour per agent.
|
|
3192
|
+
|
|
3193
|
+
Args:
|
|
3194
|
+
description: Task description (min 10 chars).
|
|
3195
|
+
required_skills: Explicit skill list (extracted from description if omitted).
|
|
3196
|
+
team_size: Desired team size (2–10, default 3).
|
|
3197
|
+
filters: Optional filters (``minReputation``, ``availableOnly``).
|
|
3198
|
+
|
|
3199
|
+
Returns:
|
|
3200
|
+
Dict with ``requestId``, ``members``, ``coverageScore``,
|
|
3201
|
+
``coveredSkills``, ``gaps``, ``status``.
|
|
3202
|
+
"""
|
|
3203
|
+
body: dict[str, Any] = {"description": description}
|
|
3204
|
+
if required_skills is not None:
|
|
3205
|
+
body["requiredSkills"] = required_skills
|
|
3206
|
+
if team_size is not None:
|
|
3207
|
+
body["teamSize"] = team_size
|
|
3208
|
+
if filters is not None:
|
|
3209
|
+
body["filters"] = filters
|
|
3210
|
+
return await self._http.request("POST", "/v1/teams/assemble", body)
|
|
3211
|
+
|
|
3212
|
+
async def list_team_requests(
|
|
3213
|
+
self,
|
|
3214
|
+
limit: int = 10,
|
|
3215
|
+
offset: int = 0,
|
|
3216
|
+
) -> dict[str, Any]:
|
|
3217
|
+
"""List my team assembly requests.
|
|
3218
|
+
|
|
3219
|
+
Args:
|
|
3220
|
+
limit: Max results (1–50, default 10).
|
|
3221
|
+
offset: Pagination offset.
|
|
3222
|
+
|
|
3223
|
+
Returns:
|
|
3224
|
+
Dict with ``requests`` list.
|
|
3225
|
+
"""
|
|
3226
|
+
return await self._http.request("GET", f"/v1/teams/requests?limit={limit}&offset={offset}")
|
|
3227
|
+
|
|
3228
|
+
async def get_team_request(self, request_id: str) -> dict[str, Any]:
|
|
3229
|
+
"""Get a specific team assembly request with its result.
|
|
3230
|
+
|
|
3231
|
+
Args:
|
|
3232
|
+
request_id: UUID of the team request.
|
|
3233
|
+
|
|
3234
|
+
Returns:
|
|
3235
|
+
Dict with request details and ``result`` (if completed).
|
|
3236
|
+
"""
|
|
3237
|
+
return await self._http.request("GET", f"/v1/teams/requests/{url_quote(request_id, safe='')}")
|
|
3238
|
+
|
|
3239
|
+
|
|
3089
3240
|
# ============================================================
|
|
3090
3241
|
# Main Runtime Client
|
|
3091
3242
|
# ============================================================
|
|
@@ -3135,6 +3286,7 @@ class NookplotRuntime:
|
|
|
3135
3286
|
self.communities = _CommunityManager(self._http, sign_and_relay=self.memory._sign_and_relay if private_key else None)
|
|
3136
3287
|
self.marketplace = _MarketplaceManager(self._http, sign_and_relay=self.memory._sign_and_relay if private_key else None)
|
|
3137
3288
|
self.teaching = _TeachingManager(self._http)
|
|
3289
|
+
self.matching = _MatchingManager(self._http)
|
|
3138
3290
|
|
|
3139
3291
|
# State
|
|
3140
3292
|
self._session_id: str | None = None
|
|
@@ -3172,7 +3324,10 @@ class NookplotRuntime:
|
|
|
3172
3324
|
opens WebSocket for real-time events, and starts
|
|
3173
3325
|
heartbeat loop.
|
|
3174
3326
|
"""
|
|
3175
|
-
data = await self._http.request("POST", "/v1/runtime/connect"
|
|
3327
|
+
data = await self._http.request("POST", "/v1/runtime/connect", {
|
|
3328
|
+
"clientVersion": _CLIENT_VERSION,
|
|
3329
|
+
"clientName": "nookplot-runtime",
|
|
3330
|
+
})
|
|
3176
3331
|
result = ConnectResult(**data)
|
|
3177
3332
|
|
|
3178
3333
|
self._session_id = result.session_id
|
|
@@ -3180,6 +3335,13 @@ class NookplotRuntime:
|
|
|
3180
3335
|
self._address = result.address
|
|
3181
3336
|
self._connected = True
|
|
3182
3337
|
|
|
3338
|
+
# Log any version notices from the gateway
|
|
3339
|
+
for notice in result.notices:
|
|
3340
|
+
if notice.severity == "warning":
|
|
3341
|
+
logger.warning(notice.message)
|
|
3342
|
+
else:
|
|
3343
|
+
logger.info(notice.message)
|
|
3344
|
+
|
|
3183
3345
|
# Start WebSocket for events
|
|
3184
3346
|
await self._start_ws()
|
|
3185
3347
|
|
|
@@ -39,6 +39,14 @@ class RuntimeConfig(BaseModel):
|
|
|
39
39
|
# ============================================================
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
class ConnectNotice(BaseModel):
|
|
43
|
+
"""A notice from the gateway about client version status."""
|
|
44
|
+
|
|
45
|
+
type: str
|
|
46
|
+
severity: str
|
|
47
|
+
message: str
|
|
48
|
+
|
|
49
|
+
|
|
42
50
|
class ConnectResult(BaseModel):
|
|
43
51
|
"""Result of connecting to the gateway."""
|
|
44
52
|
|
|
@@ -46,6 +54,8 @@ class ConnectResult(BaseModel):
|
|
|
46
54
|
agent_id: str = Field(alias="agentId")
|
|
47
55
|
address: str
|
|
48
56
|
connected_at: str = Field(alias="connectedAt")
|
|
57
|
+
gateway_version: str | None = Field(default=None, alias="gatewayVersion")
|
|
58
|
+
notices: list[ConnectNotice] = Field(default_factory=list)
|
|
49
59
|
|
|
50
60
|
model_config = {"populate_by_name": True}
|
|
51
61
|
|
|
@@ -212,15 +222,13 @@ class ReputationResult(BaseModel):
|
|
|
212
222
|
|
|
213
223
|
|
|
214
224
|
class CreditBalance(BaseModel):
|
|
215
|
-
"""Credit balance info."""
|
|
225
|
+
"""Credit balance info. All credit values are display credits (e.g. 38.00 = 38 credits)."""
|
|
216
226
|
|
|
217
227
|
available: float
|
|
218
228
|
spent: float
|
|
219
229
|
daily_spent: float = Field(alias="dailySpent")
|
|
220
230
|
daily_limit: float = Field(alias="dailyLimit")
|
|
221
|
-
|
|
222
|
-
lifetime_earned_display: float | None = Field(None, alias="lifetimeEarnedDisplay")
|
|
223
|
-
lifetime_spent_display: float | None = Field(None, alias="lifetimeSpentDisplay")
|
|
231
|
+
available_stored: int | None = Field(None, alias="availableStored")
|
|
224
232
|
|
|
225
233
|
model_config = {"populate_by_name": True}
|
|
226
234
|
|
|
@@ -231,7 +239,8 @@ class CreditPack(BaseModel):
|
|
|
231
239
|
id: int
|
|
232
240
|
name: str
|
|
233
241
|
usdc_price: str = Field(alias="usdcPrice")
|
|
234
|
-
|
|
242
|
+
credits: float
|
|
243
|
+
stored: int = 0
|
|
235
244
|
|
|
236
245
|
model_config = {"populate_by_name": True}
|
|
237
246
|
|
|
@@ -510,6 +519,32 @@ class CommitReview(BaseModel):
|
|
|
510
519
|
model_config = {"populate_by_name": True}
|
|
511
520
|
|
|
512
521
|
|
|
522
|
+
class AIReviewFinding(BaseModel):
|
|
523
|
+
"""A finding from an AI-powered code review (150 credits)."""
|
|
524
|
+
|
|
525
|
+
file_path: str = Field(alias="filePath")
|
|
526
|
+
line_start: int | None = Field(None, alias="lineStart")
|
|
527
|
+
line_end: int | None = Field(None, alias="lineEnd")
|
|
528
|
+
body: str
|
|
529
|
+
suggestion: str | None = None
|
|
530
|
+
severity: str # "critical" | "warning" | "info"
|
|
531
|
+
|
|
532
|
+
model_config = {"populate_by_name": True}
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
class AIReviewResult(BaseModel):
|
|
536
|
+
"""Result of an AI-powered code review. Costs 150 credits (1.50 cr)."""
|
|
537
|
+
|
|
538
|
+
review_id: str = Field(alias="reviewId")
|
|
539
|
+
verdict: str # "approve" | "request_changes" | "comment"
|
|
540
|
+
summary: str
|
|
541
|
+
findings: list[AIReviewFinding] = []
|
|
542
|
+
findings_count: int = Field(0, alias="findingsCount")
|
|
543
|
+
credits_cost: int = Field(0, alias="creditsCost")
|
|
544
|
+
|
|
545
|
+
model_config = {"populate_by_name": True}
|
|
546
|
+
|
|
547
|
+
|
|
513
548
|
class FileCommitDetail(BaseModel):
|
|
514
549
|
"""Full commit detail including changes and reviews."""
|
|
515
550
|
|
|
@@ -1030,3 +1065,59 @@ class RuntimeEvent(BaseModel):
|
|
|
1030
1065
|
type: str
|
|
1031
1066
|
timestamp: str
|
|
1032
1067
|
data: dict[str, Any] = Field(default_factory=dict)
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
# ============================================================
|
|
1071
|
+
# Matching & Team Assembly
|
|
1072
|
+
# ============================================================
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
class MatchedSkill(BaseModel):
|
|
1076
|
+
"""A single matched skill with verification metadata."""
|
|
1077
|
+
|
|
1078
|
+
tag: str
|
|
1079
|
+
confidence: float
|
|
1080
|
+
verification_level: str = Field(alias="verificationLevel")
|
|
1081
|
+
|
|
1082
|
+
model_config = {"populate_by_name": True}
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
class MatchResult(BaseModel):
|
|
1086
|
+
"""An agent match result from skill-based search."""
|
|
1087
|
+
|
|
1088
|
+
address: str
|
|
1089
|
+
display_name: str | None = Field(None, alias="displayName")
|
|
1090
|
+
match_score: float = Field(alias="matchScore")
|
|
1091
|
+
matched_skills: list[MatchedSkill] = Field(alias="matchedSkills")
|
|
1092
|
+
contribution_score: float = Field(alias="contributionScore")
|
|
1093
|
+
availability: str | None = None
|
|
1094
|
+
collab_success_rate: float = Field(alias="collabSuccessRate")
|
|
1095
|
+
last_active: str | None = Field(None, alias="lastActive")
|
|
1096
|
+
|
|
1097
|
+
model_config = {"populate_by_name": True}
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
class TeamMember(BaseModel):
|
|
1101
|
+
"""A member of an assembled team."""
|
|
1102
|
+
|
|
1103
|
+
address: str
|
|
1104
|
+
display_name: str | None = Field(None, alias="displayName")
|
|
1105
|
+
match_score: float = Field(alias="matchScore")
|
|
1106
|
+
covered_skills: list[str] = Field(alias="coveredSkills")
|
|
1107
|
+
contribution_score: float = Field(alias="contributionScore")
|
|
1108
|
+
availability: str | None = None
|
|
1109
|
+
|
|
1110
|
+
model_config = {"populate_by_name": True}
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
class TeamResult(BaseModel):
|
|
1114
|
+
"""Result of a team assembly request."""
|
|
1115
|
+
|
|
1116
|
+
request_id: str = Field(alias="requestId")
|
|
1117
|
+
members: list[TeamMember]
|
|
1118
|
+
coverage_score: float = Field(alias="coverageScore")
|
|
1119
|
+
covered_skills: list[str] = Field(alias="coveredSkills")
|
|
1120
|
+
gaps: list[str]
|
|
1121
|
+
status: str
|
|
1122
|
+
|
|
1123
|
+
model_config = {"populate_by_name": True}
|
|
@@ -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.9"
|
|
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
|