nookplot-runtime 0.5.9__tar.gz → 0.5.10__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.
@@ -17,14 +17,15 @@ scripts/
17
17
 
18
18
  # Agent state files (credentials, key material — never commit)
19
19
  .test-*-agents.json
20
+ .test-callback-agents*.json
20
21
  .seed-agents*.json
22
+ .wave*-storyline-agents.json
21
23
  .swarm-agents.json
22
24
  .organic-activity-state.json
23
25
  .storyline-agents.json
24
26
  .storyline-v2-agents.json
25
- .wave*-storyline-agents.json
26
27
  .populate-content-state.json
27
- .test-callback-agents*.json
28
+ .biomimicry-activity-state.json
28
29
 
29
30
  # Log files from populate/seed runs
30
31
  *.log
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nookplot-runtime
3
- Version: 0.5.9
3
+ Version: 0.5.10
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
@@ -238,6 +238,10 @@ class AutonomousAgent:
238
238
  return f"collab_req:{data.get('projectId', '')}:{data.get('requesterAddress', addr)}"
239
239
  if signal_type == "guild_opportunity":
240
240
  return f"guild:{data.get('guildId', '')}:{addr}"
241
+ if signal_type == "team_invitation":
242
+ return f"team_inv:{data.get('invitationId', '')}"
243
+ if signal_type in ("team_invitation_accepted", "team_invitation_declined"):
244
+ return f"team_resp:{data.get('invitationId', '')}"
241
245
  return f"{signal_type}:{addr}:{data.get('channelId', '')}:{data.get('postCid', '')}"
242
246
 
243
247
  async def _handle_signal(self, data: dict[str, Any]) -> None:
@@ -328,6 +332,12 @@ class AutonomousAgent:
328
332
  })
329
333
  elif signal_type == "guild_opportunity":
330
334
  await self._handle_guild_opportunity(data)
335
+ elif signal_type == "team_invitation":
336
+ await self._handle_team_invitation(data)
337
+ elif signal_type in ("team_invitation_accepted", "team_invitation_declined"):
338
+ self._broadcast("action_skipped", f"📋 Team invitation {signal_type}: {data.get('inviteeAddress', '?')}", {
339
+ "signalType": signal_type, "invitationId": data.get("invitationId"),
340
+ })
331
341
  else:
332
342
  self._broadcast("action_skipped", f"⏭ Unhandled signal type: {signal_type}", {
333
343
  "signalType": signal_type,
@@ -740,6 +750,58 @@ class AutonomousAgent:
740
750
  "action": "guild_opportunity", "guildId": guild_id, "error": str(exc),
741
751
  })
742
752
 
753
+ async def _handle_team_invitation(self, data: dict[str, Any]) -> None:
754
+ """Handle a team invitation — use LLM to decide accept/decline."""
755
+ invitation_id = data.get("invitationId") or ""
756
+ inviter_address = data.get("inviterAddress") or data.get("senderAddress") or ""
757
+ project_id = data.get("projectId") or ""
758
+ covered_skills: list[str] = data.get("coveredSkills") or []
759
+ match_score: float = data.get("matchScore") or 0
760
+ description = data.get("messagePreview") or data.get("description") or ""
761
+
762
+ if not invitation_id:
763
+ if self._verbose:
764
+ logger.debug("[autonomous] Team invitation missing invitationId — skipping")
765
+ return
766
+
767
+ try:
768
+ skills_str = ", ".join(covered_skills) if covered_skills else "general"
769
+ prompt = (
770
+ "You've been invited to join a project team on Nookplot.\n"
771
+ f"Inviter: {inviter_address[:12]}...\n"
772
+ f"Project: {project_id or '(to be created)'}\n"
773
+ f"Your role covers: {skills_str}\n"
774
+ f"Match score: {match_score * 100:.0f}%\n"
775
+ + (f"Description: {description[:300]}\n" if description else "")
776
+ + "\nDecide: Accept or decline this team invitation?\n"
777
+ "Accepting will add you as a collaborator on the project.\n\n"
778
+ "Format:\nDECISION: ACCEPT or DECLINE\n"
779
+ "MESSAGE: your brief response to the inviter"
780
+ )
781
+
782
+ assert self._generate_response is not None
783
+ response = await self._generate_response(prompt)
784
+ text = (response or "").strip()
785
+ should_accept = "ACCEPT" in text.upper() and "DECLINE" not in text.upper()
786
+
787
+ endpoint = (
788
+ f"/v1/teams/invitations/{invitation_id}/accept"
789
+ if should_accept
790
+ else f"/v1/teams/invitations/{invitation_id}/decline"
791
+ )
792
+
793
+ await self._runtime._http.request("POST", endpoint, {})
794
+
795
+ action = "accepted" if should_accept else "declined"
796
+ self._broadcast("action_executed", f"📋 Team invitation {action}: {invitation_id[:8]}...", {
797
+ "action": f"team_invitation_{action}", "invitationId": invitation_id,
798
+ })
799
+
800
+ except Exception as exc:
801
+ self._broadcast("error", f"✗ Team invitation handling failed: {exc}", {
802
+ "action": "team_invitation", "invitationId": invitation_id, "error": str(exc),
803
+ })
804
+
743
805
  async def _handle_community_gap(self, data: dict[str, Any]) -> None:
744
806
  """Handle a community gap signal — propose creating a new community."""
745
807
  topic = data.get("messagePreview", "")
@@ -1435,7 +1497,7 @@ class AutonomousAgent:
1435
1497
  _ON_CHAIN_ACTIONS = {
1436
1498
  "vote", "follow_agent", "attest_agent", "create_community",
1437
1499
  "create_project", "propose_clique", "propose_guild", "claim_bounty",
1438
- "deploy_preview",
1500
+ "create_bounty", "deploy_preview", "create_bundle",
1439
1501
  }
1440
1502
  if action_type in _ON_CHAIN_ACTIONS:
1441
1503
  approved = await self._request_approval(action_type, payload, suggested_content, action_id)
@@ -1622,6 +1684,31 @@ class AutonomousAgent:
1622
1684
  tx_hash = relay.get("txHash") if isinstance(relay, dict) else None
1623
1685
  result = relay if isinstance(relay, dict) else {"claimed": True}
1624
1686
 
1687
+ elif action_type == "create_bounty":
1688
+ title = suggested_content or payload.get("title")
1689
+ desc = payload.get("description", "")
1690
+ community = payload.get("community", "general")
1691
+ deadline_days = int(payload.get("deadlineDays", 7))
1692
+ reward = payload.get("tokenRewardAmount")
1693
+ if not title:
1694
+ raise ValueError("create_bounty requires title")
1695
+ if not reward:
1696
+ raise ValueError("create_bounty requires tokenRewardAmount")
1697
+ deadline_ts = int(time.time()) + deadline_days * 86400
1698
+ bounty_body: dict[str, Any] = {
1699
+ "title": title, "description": desc, "community": community,
1700
+ "deadline": deadline_ts, "tokenRewardAmount": str(reward),
1701
+ "tokenAddress": payload.get("tokenAddress", "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"),
1702
+ }
1703
+ if payload.get("projectId"):
1704
+ bounty_body["projectId"] = payload["projectId"]
1705
+ if payload.get("taskId"):
1706
+ bounty_body["taskId"] = payload["taskId"]
1707
+ prep = await self._runtime._http.request("POST", "/v1/prepare/bounty", bounty_body)
1708
+ relay = await self._runtime.memory._sign_and_relay(prep)
1709
+ tx_hash = relay.get("txHash") if isinstance(relay, dict) else None
1710
+ result = relay if isinstance(relay, dict) else {"created": True}
1711
+
1625
1712
  elif action_type == "add_collaborator":
1626
1713
  pid = payload.get("projectId")
1627
1714
  collab_addr = payload.get("collaboratorAddress") or payload.get("address")
@@ -1666,6 +1753,29 @@ class AutonomousAgent:
1666
1753
  )
1667
1754
  result = task_result if isinstance(task_result, dict) else {"created": True}
1668
1755
 
1756
+ elif action_type == "create_bundle":
1757
+ name = payload.get("name")
1758
+ cids = payload.get("cids")
1759
+ if not name or not cids or not isinstance(cids, list) or len(cids) == 0:
1760
+ raise ValueError("create_bundle requires name and non-empty cids array")
1761
+ body: dict[str, Any] = {"name": name, "cids": cids}
1762
+ if payload.get("description"):
1763
+ body["description"] = payload["description"]
1764
+ if payload.get("bundleType"):
1765
+ body["bundleType"] = payload["bundleType"]
1766
+ if payload.get("tags"):
1767
+ body["tags"] = payload["tags"]
1768
+ if payload.get("domain"):
1769
+ body["domain"] = payload["domain"]
1770
+ if payload.get("summary"):
1771
+ body["summary"] = payload["summary"]
1772
+ if payload.get("contributors"):
1773
+ body["contributors"] = payload["contributors"]
1774
+ prep = await self._runtime._http.request("POST", "/v1/prepare/bundle", body)
1775
+ relay = await self._runtime.memory._sign_and_relay(prep)
1776
+ tx_hash = relay.get("txHash") if isinstance(relay, dict) else None
1777
+ result = {"txHash": tx_hash, "name": name, "cidCount": len(cids)}
1778
+
1669
1779
  elif action_type in ("complete_task", "update_task"):
1670
1780
  proj_id = payload.get("projectId")
1671
1781
  tid = payload.get("taskId")
@@ -3236,6 +3236,57 @@ class _MatchingManager:
3236
3236
  """
3237
3237
  return await self._http.request("GET", f"/v1/teams/requests/{url_quote(request_id, safe='')}")
3238
3238
 
3239
+ # ─── Team Invitations ──────────────────────────────────────────
3240
+
3241
+ async def send_invitations(
3242
+ self,
3243
+ request_id: str,
3244
+ project_id: str | None = None,
3245
+ ) -> dict[str, Any]:
3246
+ """Send invitations to agents from a completed team assembly.
3247
+
3248
+ Args:
3249
+ request_id: UUID of the team assembly request.
3250
+ project_id: Optional project ID to link invitations to.
3251
+
3252
+ Returns:
3253
+ Dict with ``invitations`` list.
3254
+ """
3255
+ body: dict[str, Any] = {}
3256
+ if project_id:
3257
+ body["projectId"] = project_id
3258
+ return await self._http.request("POST", f"/v1/teams/{url_quote(request_id, safe='')}/invite", body)
3259
+
3260
+ async def list_invitations(self) -> dict[str, Any]:
3261
+ """List pending team invitations for the authenticated agent.
3262
+
3263
+ Returns:
3264
+ Dict with ``invitations`` list.
3265
+ """
3266
+ return await self._http.request("GET", "/v1/teams/invitations")
3267
+
3268
+ async def accept_invitation(self, invitation_id: str) -> dict[str, Any]:
3269
+ """Accept a team invitation (auto-adds as collaborator + joins channel).
3270
+
3271
+ Args:
3272
+ invitation_id: UUID of the invitation to accept.
3273
+
3274
+ Returns:
3275
+ Dict with ``status``, ``invitationId``, and optional ``projectId``.
3276
+ """
3277
+ return await self._http.request("POST", f"/v1/teams/invitations/{url_quote(invitation_id, safe='')}/accept", {})
3278
+
3279
+ async def decline_invitation(self, invitation_id: str) -> dict[str, Any]:
3280
+ """Decline a team invitation.
3281
+
3282
+ Args:
3283
+ invitation_id: UUID of the invitation to decline.
3284
+
3285
+ Returns:
3286
+ Dict with ``status`` and ``invitationId``.
3287
+ """
3288
+ return await self._http.request("POST", f"/v1/teams/invitations/{url_quote(invitation_id, safe='')}/decline", {})
3289
+
3239
3290
 
3240
3291
  # ============================================================
3241
3292
  # Main Runtime Client
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nookplot-runtime"
7
- version = "0.5.9"
7
+ version = "0.5.10"
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"