nookplot-runtime 0.5.66__tar.gz → 0.5.67__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.66 → nookplot_runtime-0.5.67}/PKG-INFO +1 -1
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/nookplot_runtime/__init__.py +13 -5
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/nookplot_runtime/action_catalog.py +12 -38
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/nookplot_runtime/autonomous.py +27 -172
- nookplot_runtime-0.5.67/nookplot_runtime/signal_action_map.py +215 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/pyproject.toml +1 -1
- nookplot_runtime-0.5.67/tests/test_get_available_actions.py +187 -0
- nookplot_runtime-0.5.66/tests/test_get_available_actions.py +0 -110
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/.gitignore +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/README.md +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/SKILL.md +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/nookplot_runtime/action_catalog_generated.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/nookplot_runtime/client.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/nookplot_runtime/content_safety.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/nookplot_runtime/events.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/nookplot_runtime/types.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/requirements.lock +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/tests/__init__.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/tests/helpers/__init__.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/tests/helpers/mock_runtime.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/tests/test_autonomous_action_dispatch.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/tests/test_autonomous_dedup.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/tests/test_autonomous_lifecycle.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/tests/test_client.py +0 -0
- {nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/tests/test_content_safety.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nookplot-runtime
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.67
|
|
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
|
|
@@ -42,10 +42,15 @@ from nookplot_runtime.content_safety import (
|
|
|
42
42
|
)
|
|
43
43
|
from nookplot_runtime.action_catalog import (
|
|
44
44
|
ACTION_CATALOG,
|
|
45
|
-
TOOL_PROFILES,
|
|
46
|
-
filter_by_profile,
|
|
47
45
|
format_actions_for_prompt,
|
|
48
46
|
)
|
|
47
|
+
from nookplot_runtime.signal_action_map import (
|
|
48
|
+
CORE_ACTIONS,
|
|
49
|
+
SIGNAL_CONTEXT_ACTIONS,
|
|
50
|
+
get_available_actions_from_map,
|
|
51
|
+
get_category_listing,
|
|
52
|
+
get_tools_in_category,
|
|
53
|
+
)
|
|
49
54
|
from nookplot_runtime.types import (
|
|
50
55
|
RuntimeConfig,
|
|
51
56
|
ConnectResult,
|
|
@@ -132,10 +137,13 @@ __all__ = [
|
|
|
132
137
|
"extract_safe_text",
|
|
133
138
|
"UNTRUSTED_CONTENT_INSTRUCTION",
|
|
134
139
|
"ACTION_CATALOG",
|
|
135
|
-
"TOOL_PROFILES",
|
|
136
|
-
"filter_by_profile",
|
|
137
140
|
"format_actions_for_prompt",
|
|
141
|
+
"CORE_ACTIONS",
|
|
142
|
+
"SIGNAL_CONTEXT_ACTIONS",
|
|
143
|
+
"get_available_actions_from_map",
|
|
138
144
|
"get_available_actions",
|
|
145
|
+
"get_category_listing",
|
|
146
|
+
"get_tools_in_category",
|
|
139
147
|
]
|
|
140
148
|
|
|
141
|
-
__version__ = "0.2.
|
|
149
|
+
__version__ = "0.2.24"
|
|
@@ -159,44 +159,6 @@ ACTION_CATALOG: dict[str, ActionInfo] = {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
|
|
162
|
-
# ── Tool Profiles ──
|
|
163
|
-
|
|
164
|
-
TOOL_PROFILES: dict[str, list[str]] = {
|
|
165
|
-
"core": ["identity", "discovery", "messaging", "social"],
|
|
166
|
-
"builder": ["identity", "projects", "bounties", "discovery"],
|
|
167
|
-
"economy": ["identity", "bounties", "marketplace", "economy"],
|
|
168
|
-
"coordinator": ["identity", "coordination", "bounties", "projects"],
|
|
169
|
-
"researcher": ["identity", "discovery", "memory", "autoresearch"],
|
|
170
|
-
"full": [
|
|
171
|
-
"identity", "discovery", "social", "messaging", "projects", "bounties",
|
|
172
|
-
"marketplace", "coordination", "economy", "memory", "proactive", "skills",
|
|
173
|
-
"email", "teaching", "tools", "autoresearch",
|
|
174
|
-
],
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def filter_by_profile(actions: list[str]) -> list[str]:
|
|
179
|
-
"""Filter an action list to only include actions in the active tool profile.
|
|
180
|
-
|
|
181
|
-
Reads ``NOOKPLOT_TOOL_PROFILE`` env var (default: ``"full"``).
|
|
182
|
-
Actions without a category (meta-actions like reply, ignore) pass through.
|
|
183
|
-
"""
|
|
184
|
-
import os
|
|
185
|
-
profile_name = os.environ.get("NOOKPLOT_TOOL_PROFILE", "full")
|
|
186
|
-
cats = TOOL_PROFILES.get(profile_name, TOOL_PROFILES["full"])
|
|
187
|
-
active_cats = set(cats)
|
|
188
|
-
result: list[str] = []
|
|
189
|
-
for a in actions:
|
|
190
|
-
info = ACTION_CATALOG.get(a)
|
|
191
|
-
# No catalog entry or no category → meta-action, always include
|
|
192
|
-
if not info or not info.get("category"):
|
|
193
|
-
result.append(a)
|
|
194
|
-
continue
|
|
195
|
-
if info["category"] in active_cats:
|
|
196
|
-
result.append(a)
|
|
197
|
-
return result
|
|
198
|
-
|
|
199
|
-
|
|
200
162
|
def format_actions_for_prompt(actions: list[str]) -> str:
|
|
201
163
|
"""Format a list of action names into a descriptive string for LLM prompts.
|
|
202
164
|
|
|
@@ -206,6 +168,8 @@ def format_actions_for_prompt(actions: list[str]) -> str:
|
|
|
206
168
|
- create_bounty: Create a bounty with a reward ... Params: title, description, reward, communityId
|
|
207
169
|
- reply: Send a text reply ... Params: content
|
|
208
170
|
- ignore: Skip this event and take no action.
|
|
171
|
+
|
|
172
|
+
Appends a browse hint when ``browse_tools`` is in the action list.
|
|
209
173
|
"""
|
|
210
174
|
lines: list[str] = []
|
|
211
175
|
for action in actions:
|
|
@@ -215,4 +179,14 @@ def format_actions_for_prompt(actions: list[str]) -> str:
|
|
|
215
179
|
continue
|
|
216
180
|
param_str = f" Params: {info['params']}" if info.get("params") else ""
|
|
217
181
|
lines.append(f"- {action}: {info['description']}.{param_str}")
|
|
182
|
+
|
|
183
|
+
# Add browse hint when browse_tools is available
|
|
184
|
+
if "browse_tools" in actions:
|
|
185
|
+
lines.append("")
|
|
186
|
+
lines.append(
|
|
187
|
+
"Tip: Use browse_tools to discover more tools by category "
|
|
188
|
+
"(projects, bounties, coordination, autoresearch, marketplace, "
|
|
189
|
+
"email, teaching, skills, economy, etc.)"
|
|
190
|
+
)
|
|
191
|
+
|
|
218
192
|
return "\n".join(lines)
|
|
@@ -48,7 +48,8 @@ import re
|
|
|
48
48
|
import time
|
|
49
49
|
from typing import Any, Callable, Awaitable
|
|
50
50
|
|
|
51
|
-
from .action_catalog import
|
|
51
|
+
from .action_catalog import ACTION_CATALOG
|
|
52
|
+
from .signal_action_map import CORE_ACTIONS, SIGNAL_CONTEXT_ACTIONS, get_available_actions_from_map, get_category_listing, get_tools_in_category
|
|
52
53
|
from .content_safety import sanitize_for_prompt, wrap_untrusted, UNTRUSTED_CONTENT_INSTRUCTION
|
|
53
54
|
|
|
54
55
|
logger = logging.getLogger("nookplot.autonomous")
|
|
@@ -62,12 +63,12 @@ ActivityCallback = Callable[[str, str, dict[str, Any]], Any]
|
|
|
62
63
|
ApprovalCallback = Callable[[str, dict[str, Any]], Awaitable[bool]]
|
|
63
64
|
|
|
64
65
|
|
|
65
|
-
def get_available_actions(signal_type: str) -> list[str]:
|
|
66
|
+
def get_available_actions(signal_type: str, loaded_categories: set[str] | None = None) -> list[str]:
|
|
66
67
|
"""Get the list of available actions for a given signal type.
|
|
67
68
|
|
|
68
69
|
Returns contextual actions that make sense for each signal — agents use
|
|
69
70
|
this to present valid options to their LLM instead of offering all 100+
|
|
70
|
-
actions.
|
|
71
|
+
actions. Uses the shared signal action map (single source of truth).
|
|
71
72
|
|
|
72
73
|
Example::
|
|
73
74
|
|
|
@@ -75,179 +76,12 @@ def get_available_actions(signal_type: str) -> list[str]:
|
|
|
75
76
|
from nookplot_runtime.action_catalog import format_actions_for_prompt
|
|
76
77
|
|
|
77
78
|
actions = get_available_actions("dm_received")
|
|
78
|
-
# → ["reply", "ignore"]
|
|
79
|
+
# → ["reply", "ignore", "browse_tools", ...]
|
|
79
80
|
|
|
80
81
|
prompt = format_actions_for_prompt(actions)
|
|
81
82
|
# → "- reply: Send a text reply in the current context. Params: content (string)\\n..."
|
|
82
83
|
"""
|
|
83
|
-
|
|
84
|
-
"dm_received": [
|
|
85
|
-
"reply", "send_dm", "follow_back", "attest_back", "propose_collab",
|
|
86
|
-
"vote", "publish", "create_post", "create_bounty", "create_project",
|
|
87
|
-
"create_community", "create_listing", "commit_files", "create_task",
|
|
88
|
-
"link_project_to_guild", "propose_guild", "deploy_preview",
|
|
89
|
-
"egress_request", "execute_tool", "exec_code", "call_mcp_tool", "register_webhook",
|
|
90
|
-
"workspace_create", "publish_insight",
|
|
91
|
-
"create_intent", "browse_intents",
|
|
92
|
-
"launch_token", "preview_token_launch",
|
|
93
|
-
"search_skills", "install_skill", "store_memory", "recall_memory",
|
|
94
|
-
"propose_teaching", "search_teachers",
|
|
95
|
-
"credit_hire",
|
|
96
|
-
"ignore",
|
|
97
|
-
],
|
|
98
|
-
"channel_message": [
|
|
99
|
-
"reply", "publish", "vote", "follow", "attest", "propose_collab",
|
|
100
|
-
"create_post", "create_bounty", "create_project", "commit_files",
|
|
101
|
-
"create_task", "link_project_to_guild", "propose_guild",
|
|
102
|
-
"egress_request", "execute_tool", "exec_code", "call_mcp_tool",
|
|
103
|
-
"workspace_create", "publish_insight",
|
|
104
|
-
"create_intent", "browse_intents",
|
|
105
|
-
"search_skills", "install_skill", "store_memory",
|
|
106
|
-
"ignore",
|
|
107
|
-
],
|
|
108
|
-
"channel_mention": [
|
|
109
|
-
"reply", "publish", "vote", "follow", "attest", "propose_collab",
|
|
110
|
-
"create_post", "create_bounty", "create_project", "commit_files",
|
|
111
|
-
"create_task", "link_project_to_guild", "propose_guild",
|
|
112
|
-
"egress_request", "execute_tool", "call_mcp_tool",
|
|
113
|
-
"workspace_create", "publish_insight",
|
|
114
|
-
"create_intent", "browse_intents",
|
|
115
|
-
"search_skills", "install_skill", "store_memory",
|
|
116
|
-
"ignore",
|
|
117
|
-
],
|
|
118
|
-
"project_discussion": [
|
|
119
|
-
"reply", "publish", "vote", "follow", "attest", "propose_collab",
|
|
120
|
-
"create_post", "create_bounty", "create_project", "commit_files",
|
|
121
|
-
"create_task", "link_project_to_guild", "propose_guild",
|
|
122
|
-
"egress_request", "execute_tool", "call_mcp_tool",
|
|
123
|
-
"workspace_create", "publish_insight",
|
|
124
|
-
"create_intent", "browse_intents",
|
|
125
|
-
"search_skills", "install_skill", "store_memory",
|
|
126
|
-
"ignore",
|
|
127
|
-
],
|
|
128
|
-
"new_follower": ["follow_back", "send_dm", "ignore"],
|
|
129
|
-
"attestation_received": ["attest_back", "endorse_agent", "send_dm", "ignore"],
|
|
130
|
-
"endorsement_received": ["endorse_agent", "attest_back", "send_dm", "ignore"],
|
|
131
|
-
"files_committed": ["review", "comment", "request_ai_review", "list_project_files", "read_project_file", "list_commits", "get_commit_detail", "list_merge_requests", "get_merge_request", "ignore"],
|
|
132
|
-
"pending_review": ["review", "comment", "request_ai_review", "list_project_files", "read_project_file", "list_commits", "get_commit_detail", "list_merge_requests", "get_merge_request", "ignore"],
|
|
133
|
-
"merge_request_created": ["get_merge_request", "merge_merge_request", "close_merge_request", "reply", "ignore"],
|
|
134
|
-
"project_forked": ["acknowledge", "send_dm", "ignore"],
|
|
135
|
-
"review_submitted": ["reply", "ignore"],
|
|
136
|
-
"collaborator_added": ["send_message", "reply", "ignore"],
|
|
137
|
-
"new_post_in_community": ["reply", "post_reply", "vote", "publish", "ignore"],
|
|
138
|
-
"post_reply": ["reply", "post_reply", "vote", "publish", "ignore"],
|
|
139
|
-
"reply_to_own_post": ["reply", "post_reply", "vote", "publish", "ignore"],
|
|
140
|
-
"bounty": ["claim", "apply_bounty", "create_bounty", "reply", "ignore"],
|
|
141
|
-
"community_gap": ["create_community", "ignore"],
|
|
142
|
-
"potential_friend": ["follow", "send_dm", "attest", "endorse_agent", "ignore"],
|
|
143
|
-
"attestation_opportunity": ["attest", "endorse_agent", "send_dm", "ignore"],
|
|
144
|
-
"directive": [
|
|
145
|
-
"execute", "reply", "publish", "create_project", "commit_files",
|
|
146
|
-
"create_task", "assign_task", "complete_task", "update_task",
|
|
147
|
-
"link_project_to_guild", "propose_guild", "approve_guild", "reject_guild", "leave_guild",
|
|
148
|
-
"create_bounty", "create_bundle", "propose_collab", "assemble_team",
|
|
149
|
-
"find_agents", "deploy_preview", "add_collaborator",
|
|
150
|
-
"fork_project", "create_merge_request", "list_merge_requests", "get_merge_request", "merge_merge_request", "close_merge_request",
|
|
151
|
-
"create_listing", "create_agreement", "cancel_agreement",
|
|
152
|
-
"workspace_create", "workspace_set", "workspace_snapshot",
|
|
153
|
-
"propose_action", "vote_proposal", "cancel_proposal",
|
|
154
|
-
"egress_request", "execute_tool", "exec_code", "call_mcp_tool", "connect_mcp_server", "disconnect_mcp_server", "register_webhook",
|
|
155
|
-
"publish_insight", "cite_insight", "apply_insight",
|
|
156
|
-
"deposit_treasury", "withdraw_treasury", "fund_bounty_from_treasury", "distribute_revenue",
|
|
157
|
-
"create_swarm", "claim_subtask", "submit_swarm_result", "aggregate_swarm",
|
|
158
|
-
"record_gap", "update_proficiency", "generate_recommendations",
|
|
159
|
-
"create_intent", "browse_intents", "submit_proposal", "accept_proposal", "reject_proposal",
|
|
160
|
-
"cancel_intent", "complete_intent", "withdraw_proposal", "query_oracle",
|
|
161
|
-
"launch_token", "preview_token_launch", "claim_clawnch_fees", "get_token_analytics", "report_clawnch_launch",
|
|
162
|
-
"create_search_subscription",
|
|
163
|
-
"send_email", "reply_email", "check_email", "create_email_inbox",
|
|
164
|
-
"search_skills", "publish_skill", "install_skill", "review_skill", "update_skill", "trending_skills",
|
|
165
|
-
"store_memory", "recall_memory", "list_memories", "memory_stats", "export_memories", "import_memories",
|
|
166
|
-
"forge_deploy", "forge_spawn", "forge_update_soul",
|
|
167
|
-
"propose_teaching", "accept_teaching", "deliver_teaching", "approve_teaching", "reject_teaching", "search_teachers",
|
|
168
|
-
"credit_hire", "accept_credit_agreement", "deliver_credit_work", "complete_credit_agreement", "cancel_credit_agreement",
|
|
169
|
-
"endorse_agent", "revoke_endorsement",
|
|
170
|
-
"block_agent", "unblock_agent",
|
|
171
|
-
"claim_reward",
|
|
172
|
-
"list_project_files", "read_project_file", "list_commits", "get_commit_detail",
|
|
173
|
-
"gpu_search", "gpu_heartbeat", "gpu_challenge", "gpu_submit_challenge",
|
|
174
|
-
"gpu_submit_attestation", "gpu_update_attestation", "gpu_revoke_attestation",
|
|
175
|
-
"gpu_rent",
|
|
176
|
-
"ignore",
|
|
177
|
-
],
|
|
178
|
-
"collab_request": ["add_collaborator", "propose_collab", "reply", "ignore"],
|
|
179
|
-
"service": ["reply", "update_service", "create_listing", "create_agreement", "ignore"],
|
|
180
|
-
"time_to_post": ["create_post", "create_bounty", "create_bundle", "publish_insight", "create_listing", "publish_skill", "ignore"],
|
|
181
|
-
"time_to_create_project": ["create_project", "assemble_team", "ignore"],
|
|
182
|
-
"task_assigned": ["accept", "update_task", "complete_task", "assign_task", "assemble_team", "reply", "ignore"],
|
|
183
|
-
"task_completed": ["reply", "review", "create_task", "ignore"],
|
|
184
|
-
"milestone_reached": ["reply", "ignore"],
|
|
185
|
-
"review_comment_added": ["reply", "ignore"],
|
|
186
|
-
"agent_mentioned": ["reply", "acknowledge", "ignore"],
|
|
187
|
-
"project_status_update": ["reply", "ignore"],
|
|
188
|
-
"file_shared": ["reply", "ignore"],
|
|
189
|
-
"bounty_posted_to_project": ["reply", "claim", "ignore"],
|
|
190
|
-
"bounty_access_requested": ["grant", "deny", "ignore"],
|
|
191
|
-
"bounty_access_granted": ["reply", "claim", "ignore"],
|
|
192
|
-
"project_bounty_claimed": ["reply", "ignore"],
|
|
193
|
-
"project_bounty_completed": ["reply", "ignore"],
|
|
194
|
-
"team_assembly_suggested": ["assemble_team", "ignore"],
|
|
195
|
-
"team_invitation": ["accept_invitation", "decline_invitation", "ignore"],
|
|
196
|
-
"team_invitation_accepted": ["reply", "ignore"],
|
|
197
|
-
"team_invitation_declined": ["reply", "ignore"],
|
|
198
|
-
"xmtp_message": ["reply", "ignore"],
|
|
199
|
-
# Marketplace signals
|
|
200
|
-
"agreement_created": ["deliver_work", "cancel_agreement", "send_agreement_message", "ignore"],
|
|
201
|
-
"work_delivered": ["settle_agreement", "dispute_agreement", "send_agreement_message", "expire_delivered", "ignore"],
|
|
202
|
-
"agreement_settled": ["submit_review", "ignore"],
|
|
203
|
-
"agreement_disputed": ["send_agreement_message", "expire_dispute", "ignore"],
|
|
204
|
-
"agreement_cancelled": ["ignore"],
|
|
205
|
-
"revision_requested": ["deliver_work", "send_agreement_message", "ignore"],
|
|
206
|
-
"review_received": ["ignore"],
|
|
207
|
-
# Bounty application/submission signals
|
|
208
|
-
"bounty_application_submitted": ["approve_bounty_claimer", "reject_bounty_application", "ignore"],
|
|
209
|
-
"bounty_application_approved": ["claim_bounty", "ignore"],
|
|
210
|
-
"bounty_application_rejected": ["ignore"],
|
|
211
|
-
"bounty_work_submitted": ["select_bounty_submission", "ignore"],
|
|
212
|
-
"bounty_submission_selected": ["claim_bounty", "ignore"],
|
|
213
|
-
"bounty_submission_not_selected": ["ignore"],
|
|
214
|
-
# On-chain bounty lifecycle signals
|
|
215
|
-
"bounty_claimed": ["approve_bounty_work", "approve_bounty_claimer", "dispute_bounty_work", "unclaim_bounty", "ignore"],
|
|
216
|
-
"bounty_work_approved": ["ignore"],
|
|
217
|
-
"bounty_disputed": ["cancel_bounty", "ignore"],
|
|
218
|
-
"bounty_cancelled": ["ignore"],
|
|
219
|
-
"bounty_claimer_approved": ["claim_bounty", "ignore"],
|
|
220
|
-
"guild_opportunity": ["join_guild", "approve_guild", "reject_guild", "leave_guild", "propose_guild", "link_project_to_guild", "reply", "ignore"],
|
|
221
|
-
# Intent signals
|
|
222
|
-
"intent_matched": ["submit_proposal", "browse_intents", "reply", "ignore"],
|
|
223
|
-
"proposal_received": ["accept_proposal", "reject_proposal", "reply", "ignore"],
|
|
224
|
-
"intent_accepted": ["complete_intent", "reply", "ignore"],
|
|
225
|
-
# Informational signals
|
|
226
|
-
"new_project": ["propose_collab", "reply", "ignore"],
|
|
227
|
-
"interesting_project": ["propose_collab", "reply", "ignore"],
|
|
228
|
-
"bounty_access_denied": ["ignore"],
|
|
229
|
-
"task_created": ["reply", "ignore"],
|
|
230
|
-
"task_deleted": ["reply", "ignore"],
|
|
231
|
-
"status_updated": ["reply", "ignore"],
|
|
232
|
-
"welcome_guide": ["reply", "create_post", "ignore"],
|
|
233
|
-
"onboarding_suggestion": ["reply", "ignore"],
|
|
234
|
-
"specialization_path": ["reply", "record_gap", "update_proficiency", "search_skills", "install_skill", "store_memory", "ignore"],
|
|
235
|
-
"new_bundle_in_domain": ["cite_insight", "reply", "ignore"],
|
|
236
|
-
"bundle_cited": ["ignore"],
|
|
237
|
-
"webhook_received": ["reply", "egress_request", "execute_tool", "ignore"],
|
|
238
|
-
"email_received": ["reply_email", "send_email", "send_dm", "ignore"],
|
|
239
|
-
# Teaching signals
|
|
240
|
-
"teaching_proposed": ["accept_teaching", "reject_teaching", "reply", "ignore"],
|
|
241
|
-
"teaching_accepted": ["deliver_teaching", "reply", "ignore"],
|
|
242
|
-
"teaching_delivered": ["approve_teaching", "reject_teaching", "reply", "ignore"],
|
|
243
|
-
"teaching_opportunity": ["propose_teaching", "search_teachers", "reply", "ignore"],
|
|
244
|
-
# Credit agreement signals
|
|
245
|
-
"credit_agreement_created": ["accept_credit_agreement", "cancel_credit_agreement", "reply", "ignore"],
|
|
246
|
-
"credit_work_delivered": ["complete_credit_agreement", "cancel_credit_agreement", "reply", "ignore"],
|
|
247
|
-
"credit_agreement_accepted": ["deliver_credit_work", "cancel_credit_agreement", "reply", "ignore"],
|
|
248
|
-
"bounty_opportunity": ["apply_bounty", "send_dm", "reply", "ignore"],
|
|
249
|
-
}
|
|
250
|
-
return filter_by_profile(_MAP.get(signal_type, ["reply", "ignore"]))
|
|
84
|
+
return get_available_actions_from_map(signal_type, loaded_categories or set())
|
|
251
85
|
|
|
252
86
|
|
|
253
87
|
class AutonomousAgent:
|
|
@@ -284,6 +118,8 @@ class AutonomousAgent:
|
|
|
284
118
|
self._channel_cooldowns: dict[str, float] = {}
|
|
285
119
|
# Dedup: tracks signal keys already processed. Entries expire after 1h.
|
|
286
120
|
self._processed_signals: dict[str, float] = {}
|
|
121
|
+
# Dynamic tool browsing: categories loaded via browse_tools.
|
|
122
|
+
self._loaded_categories: set[str] = set()
|
|
287
123
|
|
|
288
124
|
def start(self) -> None:
|
|
289
125
|
"""Start listening for proactive signals and action requests."""
|
|
@@ -3286,6 +3122,25 @@ class AutonomousAgent:
|
|
|
3286
3122
|
})
|
|
3287
3123
|
return
|
|
3288
3124
|
|
|
3125
|
+
# Intercept browse_tools (client-side, no gateway call needed)
|
|
3126
|
+
if action_type == "browse_tools":
|
|
3127
|
+
category = payload.get("category")
|
|
3128
|
+
if not category:
|
|
3129
|
+
listing = get_category_listing()
|
|
3130
|
+
cats = ", ".join(f"{c['name']} ({c['count']})" for c in listing)
|
|
3131
|
+
logger.info("[browse_tools] Categories: %s", cats)
|
|
3132
|
+
self._broadcast("action_executed", f"📋 browse_tools — {len(listing)} categories: {cats}", {
|
|
3133
|
+
"action": "browse_tools",
|
|
3134
|
+
})
|
|
3135
|
+
return
|
|
3136
|
+
self._loaded_categories.add(category)
|
|
3137
|
+
tools = get_tools_in_category(category)
|
|
3138
|
+
logger.info("[browse_tools] Loaded %d tools from '%s'", len(tools), category)
|
|
3139
|
+
self._broadcast("action_executed", f"📦 browse_tools — loaded {len(tools)} tools from '{category}'", {
|
|
3140
|
+
"action": "browse_tools", "category": category, "count": len(tools),
|
|
3141
|
+
})
|
|
3142
|
+
return
|
|
3143
|
+
|
|
3289
3144
|
tool_name = f"nookplot_{action_type}"
|
|
3290
3145
|
dispatch_payload: dict[str, Any] = {**payload}
|
|
3291
3146
|
if suggested_content:
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Signal Action Map — Single source of truth for which actions are available per signal type.
|
|
2
|
+
|
|
3
|
+
Replaces the duplicated ``_MAP`` dict in ``autonomous.py`` with one canonical
|
|
4
|
+
data source shared across all portals. Mirrors ``runtime/src/signalActionMap.ts``.
|
|
5
|
+
|
|
6
|
+
New tools are auto-discoverable through their ACTION_CATALOG category via
|
|
7
|
+
``browse_tools``. No dicts to update when adding new tools.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .action_catalog import ACTION_CATALOG
|
|
13
|
+
|
|
14
|
+
# ── Core Actions (~30) ──
|
|
15
|
+
# Every agent gets these on every turn, regardless of signal type.
|
|
16
|
+
# Designed for immediate usefulness without needing to browse categories.
|
|
17
|
+
|
|
18
|
+
CORE_ACTIONS: list[str] = [
|
|
19
|
+
# Meta
|
|
20
|
+
"ignore", "browse_tools",
|
|
21
|
+
# Communication
|
|
22
|
+
"reply", "send_dm", "send_message", "send_channel_message",
|
|
23
|
+
# Social
|
|
24
|
+
"follow", "vote", "comment_on_content",
|
|
25
|
+
# Discovery
|
|
26
|
+
"find_agents", "discover", "search_knowledge", "read_feed", "leaderboard",
|
|
27
|
+
# Identity
|
|
28
|
+
"my_profile", "check_balance", "update_profile",
|
|
29
|
+
# Content
|
|
30
|
+
"publish_insight", "post_content",
|
|
31
|
+
# Building (projects + bundles)
|
|
32
|
+
"create_project", "commit_files", "create_bundle",
|
|
33
|
+
"list_project_files", "read_project_file",
|
|
34
|
+
# Memory
|
|
35
|
+
"store_memory", "recall_memory",
|
|
36
|
+
# Utility
|
|
37
|
+
"check_reputation", "get_comments",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# ── Signal Context Actions ──
|
|
41
|
+
# Per-signal-type additions. Only the actions *specific* to that signal.
|
|
42
|
+
# "reply" and "ignore" are in CORE_ACTIONS — excluded here (always available).
|
|
43
|
+
# Cross-referenced across all 4 portals for consistency.
|
|
44
|
+
|
|
45
|
+
SIGNAL_CONTEXT_ACTIONS: dict[str, list[str]] = {
|
|
46
|
+
# ── Directive ──
|
|
47
|
+
# Empty: browse_tools handles expansion. Biggest win (80 → ~30 actions).
|
|
48
|
+
"directive": [],
|
|
49
|
+
|
|
50
|
+
# ── Communication ──
|
|
51
|
+
"collab_request": ["add_collaborator", "propose_collab"],
|
|
52
|
+
"xmtp_message": [],
|
|
53
|
+
"email_received": ["reply_email", "send_email"],
|
|
54
|
+
|
|
55
|
+
# ── Teams ──
|
|
56
|
+
"team_assembly_suggested": ["assemble_team"],
|
|
57
|
+
"team_invitation": ["accept_invitation", "decline_invitation"],
|
|
58
|
+
"team_invitation_accepted": [],
|
|
59
|
+
"team_invitation_declined": [],
|
|
60
|
+
|
|
61
|
+
# ── Projects ──
|
|
62
|
+
"time_to_create_project": ["assemble_team"],
|
|
63
|
+
"task_assigned": ["accept", "update_task", "complete_task", "assign_task", "assemble_team"],
|
|
64
|
+
"task_completed": ["review", "create_task"],
|
|
65
|
+
"task_created": [],
|
|
66
|
+
"task_deleted": [],
|
|
67
|
+
"milestone_reached": [],
|
|
68
|
+
"review_comment_added": [],
|
|
69
|
+
"agent_mentioned": ["acknowledge"],
|
|
70
|
+
"project_status_update": [],
|
|
71
|
+
"file_shared": [],
|
|
72
|
+
"status_updated": [],
|
|
73
|
+
"new_project": ["propose_collab"],
|
|
74
|
+
"interesting_project": ["propose_collab"],
|
|
75
|
+
|
|
76
|
+
# ── Marketplace ──
|
|
77
|
+
"service": ["update_service", "create_listing", "create_agreement"],
|
|
78
|
+
"time_to_post": ["create_post", "create_bounty", "create_listing", "publish_skill"],
|
|
79
|
+
"agreement_created": ["deliver_work", "cancel_agreement", "send_agreement_message"],
|
|
80
|
+
"work_delivered": ["settle_agreement", "dispute_agreement", "send_agreement_message", "expire_delivered"],
|
|
81
|
+
"agreement_settled": ["submit_review"],
|
|
82
|
+
"agreement_disputed": ["send_agreement_message", "expire_dispute"],
|
|
83
|
+
"agreement_cancelled": [],
|
|
84
|
+
"revision_requested": ["deliver_work", "send_agreement_message"],
|
|
85
|
+
"review_received": [],
|
|
86
|
+
|
|
87
|
+
# ── Bounties (project-scoped) ──
|
|
88
|
+
"bounty_posted_to_project": ["claim"],
|
|
89
|
+
"bounty_access_requested": ["grant", "deny"],
|
|
90
|
+
"bounty_access_granted": ["claim"],
|
|
91
|
+
"bounty_access_denied": [],
|
|
92
|
+
"project_bounty_claimed": [],
|
|
93
|
+
"project_bounty_completed": [],
|
|
94
|
+
"bounty_opportunity": ["apply_bounty", "send_dm"],
|
|
95
|
+
|
|
96
|
+
# ── Bounties (application/submission lifecycle) ──
|
|
97
|
+
"bounty_application_submitted": ["approve_bounty_claimer", "reject_bounty_application"],
|
|
98
|
+
"bounty_application_approved": ["claim_bounty"],
|
|
99
|
+
"bounty_application_rejected": [],
|
|
100
|
+
"bounty_work_submitted": ["select_bounty_submission"],
|
|
101
|
+
"bounty_submission_selected": ["claim_bounty"],
|
|
102
|
+
"bounty_submission_not_selected": [],
|
|
103
|
+
|
|
104
|
+
# ── Bounties (on-chain lifecycle) ──
|
|
105
|
+
"bounty_claimed": ["approve_bounty_work", "approve_bounty_claimer", "dispute_bounty_work", "unclaim_bounty"],
|
|
106
|
+
"bounty_work_approved": [],
|
|
107
|
+
"bounty_disputed": ["cancel_bounty"],
|
|
108
|
+
"bounty_cancelled": [],
|
|
109
|
+
"bounty_claimer_approved": ["claim_bounty"],
|
|
110
|
+
|
|
111
|
+
# ── Guilds ──
|
|
112
|
+
"guild_opportunity": ["join_guild", "approve_guild", "reject_guild", "leave_guild", "propose_guild", "link_project_to_guild"],
|
|
113
|
+
|
|
114
|
+
# ── Intents ──
|
|
115
|
+
"intent_matched": ["submit_proposal", "browse_intents"],
|
|
116
|
+
"proposal_received": ["accept_proposal", "reject_proposal"],
|
|
117
|
+
"intent_accepted": ["complete_intent"],
|
|
118
|
+
|
|
119
|
+
# ── Specialization ──
|
|
120
|
+
"specialization_path": ["record_gap", "update_proficiency", "search_skills", "install_skill"],
|
|
121
|
+
|
|
122
|
+
# ── Teaching ──
|
|
123
|
+
"teaching_proposed": ["accept_teaching", "reject_teaching"],
|
|
124
|
+
"teaching_accepted": ["deliver_teaching"],
|
|
125
|
+
"teaching_delivered": ["approve_teaching", "reject_teaching"],
|
|
126
|
+
"teaching_opportunity": ["propose_teaching", "search_teachers"],
|
|
127
|
+
|
|
128
|
+
# ── Credit Agreements ──
|
|
129
|
+
"credit_agreement_created": ["accept_credit_agreement", "cancel_credit_agreement"],
|
|
130
|
+
"credit_work_delivered": ["complete_credit_agreement", "cancel_credit_agreement"],
|
|
131
|
+
"credit_agreement_accepted": ["deliver_credit_work", "cancel_credit_agreement"],
|
|
132
|
+
|
|
133
|
+
# ── Knowledge ──
|
|
134
|
+
"new_bundle_in_domain": ["cite_insight"],
|
|
135
|
+
"bundle_cited": [],
|
|
136
|
+
|
|
137
|
+
# ── Webhooks ──
|
|
138
|
+
"webhook_received": ["egress_request", "execute_tool"],
|
|
139
|
+
|
|
140
|
+
# ── Onboarding ──
|
|
141
|
+
"welcome_guide": ["create_post"],
|
|
142
|
+
"onboarding_suggestion": [],
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_available_actions_from_map(
|
|
147
|
+
signal_type: str,
|
|
148
|
+
loaded_categories: set[str],
|
|
149
|
+
) -> list[str]:
|
|
150
|
+
"""Derive the full list of available actions for a given signal type.
|
|
151
|
+
|
|
152
|
+
Combines:
|
|
153
|
+
1. CORE_ACTIONS (always available, ~30)
|
|
154
|
+
2. SIGNAL_CONTEXT_ACTIONS for the signal type (contextual, 0-6)
|
|
155
|
+
3. All actions from loaded categories (dynamically loaded via browse_tools)
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
signal_type: The signal type (e.g. "directive", "bounty_claimed")
|
|
159
|
+
loaded_categories: Set of category names loaded via browse_tools
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Deduplicated list of action names
|
|
163
|
+
"""
|
|
164
|
+
actions: set[str] = set(CORE_ACTIONS)
|
|
165
|
+
|
|
166
|
+
# Add signal-specific context actions
|
|
167
|
+
context_actions = SIGNAL_CONTEXT_ACTIONS.get(signal_type)
|
|
168
|
+
if context_actions is not None:
|
|
169
|
+
actions.update(context_actions)
|
|
170
|
+
# Unknown signal type — no extra context (core is sufficient)
|
|
171
|
+
|
|
172
|
+
# Add all actions from loaded categories
|
|
173
|
+
if loaded_categories:
|
|
174
|
+
for name, info in ACTION_CATALOG.items():
|
|
175
|
+
cat = info.get("category")
|
|
176
|
+
if cat and cat in loaded_categories:
|
|
177
|
+
actions.add(name)
|
|
178
|
+
|
|
179
|
+
return list(actions)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ── Category Helpers ──
|
|
183
|
+
|
|
184
|
+
def get_category_listing() -> list[dict[str, int | str]]:
|
|
185
|
+
"""List all tool categories with their tool counts.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List of dicts with 'name' and 'count' keys, sorted by count descending.
|
|
189
|
+
"""
|
|
190
|
+
counts: dict[str, int] = {}
|
|
191
|
+
for info in ACTION_CATALOG.values():
|
|
192
|
+
cat = info.get("category")
|
|
193
|
+
if cat:
|
|
194
|
+
counts[cat] = counts.get(cat, 0) + 1
|
|
195
|
+
return sorted(
|
|
196
|
+
[{"name": name, "count": count} for name, count in counts.items()],
|
|
197
|
+
key=lambda x: x["count"],
|
|
198
|
+
reverse=True,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def get_tools_in_category(category: str) -> list[str]:
|
|
203
|
+
"""Get all action names in a specific category.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
category: Category name (e.g. "projects", "bounties")
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
List of action names in that category.
|
|
210
|
+
"""
|
|
211
|
+
return [
|
|
212
|
+
name
|
|
213
|
+
for name, info in ACTION_CATALOG.items()
|
|
214
|
+
if info.get("category") == category
|
|
215
|
+
]
|
|
@@ -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.67"
|
|
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"
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Tests for get_available_actions() — verifies CORE_ACTIONS + signal context behaviour."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from nookplot_runtime.autonomous import get_available_actions
|
|
5
|
+
from nookplot_runtime.signal_action_map import (
|
|
6
|
+
CORE_ACTIONS,
|
|
7
|
+
SIGNAL_CONTEXT_ACTIONS,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestGetAvailableActions:
|
|
12
|
+
# ── Core always present ──
|
|
13
|
+
|
|
14
|
+
def test_always_includes_core_actions(self):
|
|
15
|
+
signals = [
|
|
16
|
+
"directive", "collab_request", "agreement_created",
|
|
17
|
+
"bounty_claimed", "guild_opportunity", "totally_unknown_signal",
|
|
18
|
+
]
|
|
19
|
+
for sig in signals:
|
|
20
|
+
actions = get_available_actions(sig)
|
|
21
|
+
for core in CORE_ACTIONS:
|
|
22
|
+
assert core in actions, f"'{core}' missing for signal '{sig}'"
|
|
23
|
+
|
|
24
|
+
def test_always_includes_ignore_and_reply(self):
|
|
25
|
+
actions = get_available_actions("directive")
|
|
26
|
+
assert "ignore" in actions
|
|
27
|
+
assert "reply" in actions
|
|
28
|
+
|
|
29
|
+
def test_always_includes_browse_tools(self):
|
|
30
|
+
assert "browse_tools" in get_available_actions("directive")
|
|
31
|
+
assert "browse_tools" in get_available_actions("bounty_claimed")
|
|
32
|
+
assert "browse_tools" in get_available_actions("unknown_signal")
|
|
33
|
+
|
|
34
|
+
def test_building_tools_always_available(self):
|
|
35
|
+
building = ["create_project", "commit_files", "create_bundle",
|
|
36
|
+
"list_project_files", "read_project_file"]
|
|
37
|
+
for tool in building:
|
|
38
|
+
assert tool in CORE_ACTIONS
|
|
39
|
+
assert tool in get_available_actions("directive")
|
|
40
|
+
assert tool in get_available_actions("unknown_signal")
|
|
41
|
+
|
|
42
|
+
# ── Directive ──
|
|
43
|
+
|
|
44
|
+
def test_directive_returns_only_core(self):
|
|
45
|
+
"""directive has empty context — browse_tools handles expansion."""
|
|
46
|
+
actions = get_available_actions("directive")
|
|
47
|
+
assert len(actions) == len(CORE_ACTIONS)
|
|
48
|
+
assert set(actions) == set(CORE_ACTIONS)
|
|
49
|
+
|
|
50
|
+
# ── Signal context additions ──
|
|
51
|
+
|
|
52
|
+
def test_agreement_created(self):
|
|
53
|
+
actions = get_available_actions("agreement_created")
|
|
54
|
+
assert "deliver_work" in actions
|
|
55
|
+
assert "cancel_agreement" in actions
|
|
56
|
+
assert "send_agreement_message" in actions
|
|
57
|
+
|
|
58
|
+
def test_work_delivered(self):
|
|
59
|
+
actions = get_available_actions("work_delivered")
|
|
60
|
+
assert "settle_agreement" in actions
|
|
61
|
+
assert "dispute_agreement" in actions
|
|
62
|
+
assert "send_agreement_message" in actions
|
|
63
|
+
assert "expire_delivered" in actions
|
|
64
|
+
|
|
65
|
+
def test_agreement_settled(self):
|
|
66
|
+
assert "submit_review" in get_available_actions("agreement_settled")
|
|
67
|
+
|
|
68
|
+
def test_revision_requested(self):
|
|
69
|
+
actions = get_available_actions("revision_requested")
|
|
70
|
+
assert "deliver_work" in actions
|
|
71
|
+
assert "send_agreement_message" in actions
|
|
72
|
+
|
|
73
|
+
def test_bounty_application_submitted(self):
|
|
74
|
+
actions = get_available_actions("bounty_application_submitted")
|
|
75
|
+
assert "approve_bounty_claimer" in actions
|
|
76
|
+
assert "reject_bounty_application" in actions
|
|
77
|
+
|
|
78
|
+
def test_bounty_claimed(self):
|
|
79
|
+
actions = get_available_actions("bounty_claimed")
|
|
80
|
+
assert "approve_bounty_work" in actions
|
|
81
|
+
assert "approve_bounty_claimer" in actions
|
|
82
|
+
assert "dispute_bounty_work" in actions
|
|
83
|
+
assert "unclaim_bounty" in actions
|
|
84
|
+
|
|
85
|
+
def test_team_invitation(self):
|
|
86
|
+
actions = get_available_actions("team_invitation")
|
|
87
|
+
assert "accept_invitation" in actions
|
|
88
|
+
assert "decline_invitation" in actions
|
|
89
|
+
|
|
90
|
+
def test_guild_opportunity(self):
|
|
91
|
+
actions = get_available_actions("guild_opportunity")
|
|
92
|
+
assert "join_guild" in actions
|
|
93
|
+
assert "approve_guild" in actions
|
|
94
|
+
assert "reject_guild" in actions
|
|
95
|
+
assert "leave_guild" in actions
|
|
96
|
+
assert "propose_guild" in actions
|
|
97
|
+
assert "link_project_to_guild" in actions
|
|
98
|
+
|
|
99
|
+
def test_webhook_received(self):
|
|
100
|
+
actions = get_available_actions("webhook_received")
|
|
101
|
+
assert "egress_request" in actions
|
|
102
|
+
assert "execute_tool" in actions
|
|
103
|
+
|
|
104
|
+
def test_collab_request(self):
|
|
105
|
+
actions = get_available_actions("collab_request")
|
|
106
|
+
assert "add_collaborator" in actions
|
|
107
|
+
assert "propose_collab" in actions
|
|
108
|
+
|
|
109
|
+
def test_time_to_post(self):
|
|
110
|
+
actions = get_available_actions("time_to_post")
|
|
111
|
+
assert "create_post" in actions
|
|
112
|
+
assert "create_bounty" in actions
|
|
113
|
+
assert "create_listing" in actions
|
|
114
|
+
assert "publish_skill" in actions
|
|
115
|
+
# publish_insight is in CORE, should also be present
|
|
116
|
+
assert "publish_insight" in actions
|
|
117
|
+
|
|
118
|
+
def test_intent_matched(self):
|
|
119
|
+
actions = get_available_actions("intent_matched")
|
|
120
|
+
assert "submit_proposal" in actions
|
|
121
|
+
assert "browse_intents" in actions
|
|
122
|
+
|
|
123
|
+
def test_teaching_signals(self):
|
|
124
|
+
assert "accept_teaching" in get_available_actions("teaching_proposed")
|
|
125
|
+
assert "reject_teaching" in get_available_actions("teaching_proposed")
|
|
126
|
+
assert "deliver_teaching" in get_available_actions("teaching_accepted")
|
|
127
|
+
assert "approve_teaching" in get_available_actions("teaching_delivered")
|
|
128
|
+
assert "propose_teaching" in get_available_actions("teaching_opportunity")
|
|
129
|
+
assert "search_teachers" in get_available_actions("teaching_opportunity")
|
|
130
|
+
|
|
131
|
+
def test_specialization_path(self):
|
|
132
|
+
actions = get_available_actions("specialization_path")
|
|
133
|
+
assert "record_gap" in actions
|
|
134
|
+
assert "update_proficiency" in actions
|
|
135
|
+
assert "search_skills" in actions
|
|
136
|
+
assert "install_skill" in actions
|
|
137
|
+
|
|
138
|
+
def test_credit_agreement_signals(self):
|
|
139
|
+
assert "accept_credit_agreement" in get_available_actions("credit_agreement_created")
|
|
140
|
+
assert "deliver_credit_work" in get_available_actions("credit_agreement_accepted")
|
|
141
|
+
assert "complete_credit_agreement" in get_available_actions("credit_work_delivered")
|
|
142
|
+
|
|
143
|
+
# ── Structural properties ──
|
|
144
|
+
|
|
145
|
+
def test_unknown_signal_returns_only_core(self):
|
|
146
|
+
actions = get_available_actions("totally_unknown_signal")
|
|
147
|
+
assert len(actions) == len(CORE_ACTIONS)
|
|
148
|
+
assert set(actions) == set(CORE_ACTIONS)
|
|
149
|
+
|
|
150
|
+
def test_context_actions_are_additive(self):
|
|
151
|
+
"""Signals with context always have >= CORE_ACTIONS."""
|
|
152
|
+
for signal, ctx in SIGNAL_CONTEXT_ACTIONS.items():
|
|
153
|
+
if ctx:
|
|
154
|
+
actions = get_available_actions(signal)
|
|
155
|
+
assert len(actions) >= len(CORE_ACTIONS)
|
|
156
|
+
|
|
157
|
+
def test_signal_with_context_has_correct_count(self):
|
|
158
|
+
"""Actions = CORE + context (deduped)."""
|
|
159
|
+
for signal, ctx in SIGNAL_CONTEXT_ACTIONS.items():
|
|
160
|
+
if ctx:
|
|
161
|
+
actions = get_available_actions(signal)
|
|
162
|
+
expected = set(CORE_ACTIONS) | set(ctx)
|
|
163
|
+
assert len(actions) == len(expected), f"Mismatch for '{signal}'"
|
|
164
|
+
|
|
165
|
+
def test_actions_are_deduplicated(self):
|
|
166
|
+
for signal in ["directive", "agreement_created", "bounty_claimed", "guild_opportunity"]:
|
|
167
|
+
actions = get_available_actions(signal)
|
|
168
|
+
assert len(set(actions)) == len(actions), f"Duplicates in '{signal}'"
|
|
169
|
+
|
|
170
|
+
def test_all_lists_non_empty(self):
|
|
171
|
+
signals = [
|
|
172
|
+
"directive", "collab_request", "agreement_created",
|
|
173
|
+
"agreement_cancelled", "review_received",
|
|
174
|
+
"bounty_application_rejected", "totally_unknown_signal",
|
|
175
|
+
]
|
|
176
|
+
for sig in signals:
|
|
177
|
+
assert len(get_available_actions(sig)) > 0
|
|
178
|
+
|
|
179
|
+
def test_loaded_categories_expand_actions(self):
|
|
180
|
+
base = get_available_actions("directive")
|
|
181
|
+
expanded = get_available_actions("directive", {"projects"})
|
|
182
|
+
assert len(expanded) >= len(base)
|
|
183
|
+
|
|
184
|
+
def test_all_signal_context_types_handled(self):
|
|
185
|
+
for signal in SIGNAL_CONTEXT_ACTIONS:
|
|
186
|
+
actions = get_available_actions(signal)
|
|
187
|
+
assert len(actions) >= len(CORE_ACTIONS)
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"""Tests for get_available_actions() — verifies contextual action lists per signal type."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from nookplot_runtime.autonomous import get_available_actions
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class TestGetAvailableActions:
|
|
8
|
-
def test_dm_received_rich_action_set(self):
|
|
9
|
-
actions = get_available_actions("dm_received")
|
|
10
|
-
assert "reply" in actions
|
|
11
|
-
assert "create_project" in actions
|
|
12
|
-
assert "link_project_to_guild" in actions
|
|
13
|
-
assert "propose_guild" in actions
|
|
14
|
-
assert "create_bounty" in actions
|
|
15
|
-
assert "execute_tool" in actions
|
|
16
|
-
assert len(actions) > 15
|
|
17
|
-
assert actions[-1] == "ignore"
|
|
18
|
-
|
|
19
|
-
def test_channel_message_expanded(self):
|
|
20
|
-
actions = get_available_actions("channel_message")
|
|
21
|
-
assert "reply" in actions
|
|
22
|
-
assert "publish" in actions
|
|
23
|
-
assert "create_project" in actions
|
|
24
|
-
assert "link_project_to_guild" in actions
|
|
25
|
-
assert len(actions) > 10
|
|
26
|
-
|
|
27
|
-
def test_new_follower(self):
|
|
28
|
-
actions = get_available_actions("new_follower")
|
|
29
|
-
assert "follow_back" in actions
|
|
30
|
-
assert "send_dm" in actions
|
|
31
|
-
|
|
32
|
-
def test_attestation_received(self):
|
|
33
|
-
actions = get_available_actions("attestation_received")
|
|
34
|
-
assert "attest_back" in actions
|
|
35
|
-
|
|
36
|
-
def test_files_committed(self):
|
|
37
|
-
actions = get_available_actions("files_committed")
|
|
38
|
-
assert "review" in actions
|
|
39
|
-
assert "request_ai_review" in actions
|
|
40
|
-
|
|
41
|
-
def test_bounty(self):
|
|
42
|
-
actions = get_available_actions("bounty")
|
|
43
|
-
assert "claim" in actions
|
|
44
|
-
assert "apply_bounty" in actions
|
|
45
|
-
|
|
46
|
-
def test_directive_has_many_actions(self):
|
|
47
|
-
actions = get_available_actions("directive")
|
|
48
|
-
assert len(actions) >= 40
|
|
49
|
-
assert "execute" in actions
|
|
50
|
-
assert "create_project" in actions
|
|
51
|
-
assert "egress_request" in actions
|
|
52
|
-
assert "report_clawnch_launch" in actions
|
|
53
|
-
assert "query_oracle" in actions
|
|
54
|
-
assert actions[-1] == "ignore"
|
|
55
|
-
|
|
56
|
-
def test_unknown_type_returns_fallback(self):
|
|
57
|
-
actions = get_available_actions("totally_unknown_signal")
|
|
58
|
-
assert actions == ["reply", "ignore"]
|
|
59
|
-
|
|
60
|
-
def test_agreement_created(self):
|
|
61
|
-
actions = get_available_actions("agreement_created")
|
|
62
|
-
assert "deliver_work" in actions
|
|
63
|
-
assert "cancel_agreement" in actions
|
|
64
|
-
|
|
65
|
-
def test_work_delivered(self):
|
|
66
|
-
actions = get_available_actions("work_delivered")
|
|
67
|
-
assert "settle_agreement" in actions
|
|
68
|
-
assert "dispute_agreement" in actions
|
|
69
|
-
|
|
70
|
-
def test_time_to_post(self):
|
|
71
|
-
actions = get_available_actions("time_to_post")
|
|
72
|
-
assert "create_post" in actions
|
|
73
|
-
assert "publish_insight" in actions
|
|
74
|
-
|
|
75
|
-
def test_bounty_application_submitted(self):
|
|
76
|
-
actions = get_available_actions("bounty_application_submitted")
|
|
77
|
-
assert "approve_bounty_claimer" in actions
|
|
78
|
-
assert "reject_bounty_application" in actions
|
|
79
|
-
|
|
80
|
-
def test_bounty_claimed(self):
|
|
81
|
-
actions = get_available_actions("bounty_claimed")
|
|
82
|
-
assert "approve_bounty_work" in actions
|
|
83
|
-
assert "dispute_bounty_work" in actions
|
|
84
|
-
|
|
85
|
-
def test_guild_opportunity(self):
|
|
86
|
-
actions = get_available_actions("guild_opportunity")
|
|
87
|
-
assert "join_guild" in actions
|
|
88
|
-
assert "propose_guild" in actions
|
|
89
|
-
|
|
90
|
-
def test_intent_matched(self):
|
|
91
|
-
actions = get_available_actions("intent_matched")
|
|
92
|
-
assert "submit_proposal" in actions
|
|
93
|
-
assert "browse_intents" in actions
|
|
94
|
-
|
|
95
|
-
def test_all_lists_end_with_ignore(self):
|
|
96
|
-
"""Every signal type's action list should end with 'ignore'."""
|
|
97
|
-
signal_types = [
|
|
98
|
-
"dm_received", "channel_message", "new_follower",
|
|
99
|
-
"files_committed", "bounty", "directive",
|
|
100
|
-
"agreement_created", "work_delivered",
|
|
101
|
-
"time_to_post", "guild_opportunity",
|
|
102
|
-
]
|
|
103
|
-
for sig in signal_types:
|
|
104
|
-
actions = get_available_actions(sig)
|
|
105
|
-
assert actions[-1] == "ignore", f"{sig} does not end with 'ignore'"
|
|
106
|
-
|
|
107
|
-
def test_webhook_received(self):
|
|
108
|
-
actions = get_available_actions("webhook_received")
|
|
109
|
-
assert "egress_request" in actions
|
|
110
|
-
assert "execute_tool" in actions
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/nookplot_runtime/action_catalog_generated.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.66 → nookplot_runtime-0.5.67}/tests/test_autonomous_action_dispatch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|