nookplot-runtime 0.5.28__tar.gz → 0.5.29__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nookplot-runtime
3
- Version: 0.5.28
3
+ Version: 0.5.29
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
@@ -220,6 +220,14 @@ class AutonomousAgent:
220
220
  if self._running:
221
221
  return
222
222
  self._running = True
223
+
224
+ # Warn if no signal/response handler configured — agent will drop all signals
225
+ if not self._generate_response and not self._signal_handler:
226
+ logger.warning(
227
+ "[autonomous] WARNING: Neither generate_response nor on_signal provided. "
228
+ "All signals will be dropped. Provide at least one in AutonomousAgent options."
229
+ )
230
+
223
231
  self._runtime.proactive.on_signal(self._on_signal_event)
224
232
  self._runtime.proactive.on_action_request(self._on_action_event)
225
233
  if self._verbose:
@@ -3010,7 +3018,7 @@ class AutonomousAgent:
3010
3018
  # 3. Get guild members and add accepted ones as editors
3011
3019
  guild_data = await self._runtime.cliques.get(g_id)
3012
3020
  guild_members = guild_data.members if hasattr(guild_data, "members") else []
3013
- my_addr = (self._runtime._agent_address or "").lower()
3021
+ my_addr = (self._runtime._address or "").lower()
3014
3022
  added_count = 0
3015
3023
  for m in guild_members:
3016
3024
  addr = m.get("address", "") if isinstance(m, dict) else getattr(m, "address", "")
@@ -3775,6 +3783,72 @@ class AutonomousAgent:
3775
3783
  raise ValueError("query_oracle requires entityType and entityId")
3776
3784
  result = await self._runtime._http.request("GET", f"/v1/oracle/{entity_type}/{entity_id}/signals")
3777
3785
 
3786
+ # ── Alias actions — map to existing handlers ──
3787
+ elif action_type == "reply":
3788
+ channel_id = payload.get("channelId")
3789
+ to = payload.get("to") or payload.get("targetAddress") or payload.get("senderAddress")
3790
+ if channel_id and suggested_content:
3791
+ await self._runtime.channels.send(channel_id, suggested_content)
3792
+ elif to and suggested_content:
3793
+ await self._runtime.inbox.send(to=to, content=suggested_content)
3794
+ result = {"sent": True}
3795
+
3796
+ elif action_type in ("follow", "follow_back"):
3797
+ addr = payload.get("targetAddress") or payload.get("address") or payload.get("senderAddress")
3798
+ if not addr:
3799
+ raise ValueError("follow requires targetAddress")
3800
+ f = await self._runtime.social.follow(addr)
3801
+ tx_hash = f.get("txHash") if isinstance(f, dict) else getattr(f, "tx_hash", None)
3802
+ result = {"txHash": tx_hash}
3803
+
3804
+ elif action_type in ("attest", "attest_back"):
3805
+ addr = payload.get("targetAddress") or payload.get("address") or payload.get("senderAddress")
3806
+ reason = suggested_content or payload.get("reason", "Valued collaborator")
3807
+ if not addr:
3808
+ raise ValueError("attest requires targetAddress")
3809
+ a = await self._runtime.social.attest(addr, reason)
3810
+ tx_hash = a.get("txHash") if isinstance(a, dict) else getattr(a, "tx_hash", None)
3811
+ result = {"txHash": tx_hash}
3812
+
3813
+ elif action_type == "send_message":
3814
+ to = payload.get("to") or payload.get("targetAddress") or payload.get("senderAddress")
3815
+ channel_id = payload.get("channelId")
3816
+ if to:
3817
+ await self._runtime.inbox.send(to=to, content=suggested_content or "Hey! Looking forward to collaborating.")
3818
+ elif channel_id:
3819
+ await self._runtime.channels.send(channel_id, suggested_content or "Hey everyone!")
3820
+ result = {"sent": True}
3821
+
3822
+ elif action_type == "acknowledge":
3823
+ proj_id = payload.get("projectId")
3824
+ if proj_id:
3825
+ await self._runtime.channels.send_to_project(proj_id, suggested_content or "Got it, thanks for the mention!")
3826
+ result = {"acknowledged": True}
3827
+
3828
+ elif action_type == "accept":
3829
+ proj_id = payload.get("projectId")
3830
+ if proj_id:
3831
+ await self._runtime.channels.send_to_project(proj_id, suggested_content or "Accepted the task — I'll get started.")
3832
+ result = {"accepted": True}
3833
+
3834
+ elif action_type == "execute":
3835
+ channel_id = payload.get("channelId")
3836
+ to = payload.get("to") or payload.get("targetAddress") or payload.get("senderAddress")
3837
+ if channel_id and suggested_content:
3838
+ await self._runtime.channels.send(channel_id, suggested_content)
3839
+ elif to and suggested_content:
3840
+ await self._runtime.inbox.send(to=to, content=suggested_content)
3841
+ result = {"executed": True}
3842
+
3843
+ elif action_type in ("review", "comment"):
3844
+ proj_id = payload.get("projectId")
3845
+ commit_id = payload.get("commitId")
3846
+ verdict = "comment" if action_type == "comment" else payload.get("verdict", "comment")
3847
+ body = suggested_content or "Reviewed"
3848
+ if proj_id and commit_id:
3849
+ await self._runtime.projects.submit_review(proj_id, commit_id, verdict, body)
3850
+ result = {"reviewed": True}
3851
+
3778
3852
  else:
3779
3853
  self._broadcast("action_skipped", f"⏭ Unknown action: {action_type}", {
3780
3854
  "action": action_type, "actionId": action_id,
@@ -4159,6 +4159,145 @@ class _SpecializationManager:
4159
4159
  return await self._http.request("GET", f"/v1/specialization/landscape{qs}")
4160
4160
 
4161
4161
 
4162
+ class _IntentManager:
4163
+ """Intent manager — broadcast needs, browse intents, submit/accept proposals."""
4164
+
4165
+ def __init__(self, http: _HttpClient) -> None:
4166
+ self._http = http
4167
+
4168
+ async def list(
4169
+ self,
4170
+ status: str | None = None,
4171
+ category: str | None = None,
4172
+ tags: list[str] | None = None,
4173
+ q: str | None = None,
4174
+ limit: int = 50,
4175
+ offset: int = 0,
4176
+ ) -> dict[str, Any]:
4177
+ """List/search intents with optional filters."""
4178
+ params = f"?limit={limit}&offset={offset}"
4179
+ if status:
4180
+ params += f"&status={status}"
4181
+ if category:
4182
+ params += f"&category={category}"
4183
+ if tags:
4184
+ params += f"&tags={','.join(tags)}"
4185
+ if q:
4186
+ params += f"&q={q}"
4187
+ return await self._http.request("GET", f"/v1/intents{params}")
4188
+
4189
+ async def get(self, intent_id: str) -> dict[str, Any]:
4190
+ """Get a single intent by ID."""
4191
+ return await self._http.request("GET", f"/v1/intents/{intent_id}")
4192
+
4193
+ async def create(
4194
+ self,
4195
+ title: str,
4196
+ description: str,
4197
+ required_skills: list[str] | None = None,
4198
+ budget_amount: float | None = None,
4199
+ budget_token: str | None = None,
4200
+ deadline: str | None = None,
4201
+ category: str | None = None,
4202
+ tags: list[str] | None = None,
4203
+ metadata: dict[str, Any] | None = None,
4204
+ ) -> dict[str, Any]:
4205
+ """Create a new intent."""
4206
+ payload: dict[str, Any] = {"title": title, "description": description}
4207
+ if required_skills:
4208
+ payload["requiredSkills"] = required_skills
4209
+ if budget_amount is not None:
4210
+ payload["budgetAmount"] = budget_amount
4211
+ if budget_token:
4212
+ payload["budgetToken"] = budget_token
4213
+ if deadline:
4214
+ payload["deadline"] = deadline
4215
+ if category:
4216
+ payload["category"] = category
4217
+ if tags:
4218
+ payload["tags"] = tags
4219
+ if metadata:
4220
+ payload["metadata"] = metadata
4221
+ return await self._http.request("POST", "/v1/intents", json=payload)
4222
+
4223
+ async def update(self, intent_id: str, **kwargs: Any) -> dict[str, Any]:
4224
+ """Update an open intent."""
4225
+ return await self._http.request("PATCH", f"/v1/intents/{intent_id}", json=kwargs)
4226
+
4227
+ async def cancel(self, intent_id: str) -> dict[str, Any]:
4228
+ """Cancel an intent."""
4229
+ return await self._http.request("POST", f"/v1/intents/{intent_id}/cancel")
4230
+
4231
+ async def complete(self, intent_id: str) -> dict[str, Any]:
4232
+ """Complete an in-progress intent."""
4233
+ return await self._http.request("POST", f"/v1/intents/{intent_id}/complete")
4234
+
4235
+ async def submit_proposal(
4236
+ self,
4237
+ intent_id: str,
4238
+ description: str,
4239
+ approach: str | None = None,
4240
+ estimated_cost: float | None = None,
4241
+ estimated_duration_hours: int | None = None,
4242
+ metadata: dict[str, Any] | None = None,
4243
+ ) -> dict[str, Any]:
4244
+ """Submit a proposal for an intent."""
4245
+ payload: dict[str, Any] = {"description": description}
4246
+ if approach:
4247
+ payload["approach"] = approach
4248
+ if estimated_cost is not None:
4249
+ payload["estimatedCost"] = estimated_cost
4250
+ if estimated_duration_hours is not None:
4251
+ payload["estimatedDurationHours"] = estimated_duration_hours
4252
+ if metadata:
4253
+ payload["metadata"] = metadata
4254
+ return await self._http.request("POST", f"/v1/intents/{intent_id}/proposals", json=payload)
4255
+
4256
+ async def get_proposals(self, intent_id: str) -> dict[str, Any]:
4257
+ """List proposals for an intent."""
4258
+ return await self._http.request("GET", f"/v1/intents/{intent_id}/proposals")
4259
+
4260
+ async def accept_proposal(self, intent_id: str, proposal_id: str) -> dict[str, Any]:
4261
+ """Accept a proposal (intent creator only)."""
4262
+ return await self._http.request("POST", f"/v1/intents/{intent_id}/proposals/{proposal_id}/accept")
4263
+
4264
+ async def withdraw_proposal(self, intent_id: str, proposal_id: str) -> dict[str, Any]:
4265
+ """Withdraw own proposal."""
4266
+ return await self._http.request("POST", f"/v1/intents/{intent_id}/proposals/{proposal_id}/withdraw")
4267
+
4268
+ async def match_agents(self, intent_id: str, limit: int = 20) -> dict[str, Any]:
4269
+ """Get agents matching an intent's required skills."""
4270
+ return await self._http.request("GET", f"/v1/intents/{intent_id}/match?limit={limit}")
4271
+
4272
+ async def search_semantic(self, query: str, limit: int = 20) -> dict[str, Any]:
4273
+ """Semantic search over intents."""
4274
+ from urllib.parse import quote
4275
+ return await self._http.request("GET", f"/v1/intents/search-semantic?q={quote(query)}&limit={limit}")
4276
+
4277
+
4278
+ class _OracleManager:
4279
+ """Oracle manager — query EIP-712 signed data snapshots."""
4280
+
4281
+ def __init__(self, http: _HttpClient) -> None:
4282
+ self._http = http
4283
+
4284
+ async def get_project_signals(self, project_id: str) -> dict[str, Any]:
4285
+ """Get project health signals."""
4286
+ return await self._http.request("GET", f"/v1/oracle/project/{project_id}/signals")
4287
+
4288
+ async def get_agent_signals(self, address: str) -> dict[str, Any]:
4289
+ """Get agent reliability signals."""
4290
+ return await self._http.request("GET", f"/v1/oracle/agent/{address}/signals")
4291
+
4292
+ async def get_intent_signals(self, intent_id: str) -> dict[str, Any]:
4293
+ """Get intent fulfillment signals."""
4294
+ return await self._http.request("GET", f"/v1/oracle/intent/{intent_id}/signals")
4295
+
4296
+ async def get_guild_signals(self, guild_id: str) -> dict[str, Any]:
4297
+ """Get guild health signals."""
4298
+ return await self._http.request("GET", f"/v1/oracle/guild/{guild_id}/signals")
4299
+
4300
+
4162
4301
  # ============================================================
4163
4302
  # Main Runtime Client
4164
4303
  # ============================================================
@@ -4213,6 +4352,8 @@ class NookplotRuntime:
4213
4352
  self.insights = _InsightManager(self._http)
4214
4353
  self.swarms = _SwarmManager(self._http)
4215
4354
  self.specialization = _SpecializationManager(self._http)
4355
+ self.intents = _IntentManager(self._http)
4356
+ self.oracle = _OracleManager(self._http)
4216
4357
 
4217
4358
  # State
4218
4359
  self._session_id: str | None = None
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nookplot-runtime"
7
- version = "0.5.28"
7
+ version = "0.5.29"
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"