better-notion 2.2.0__py3-none-any.whl → 2.3.1__py3-none-any.whl
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.
- better_notion/plugins/official/agents.py +68 -0
- better_notion/plugins/official/agents_cli.py +347 -0
- better_notion/plugins/official/agents_sdk/agent.py +503 -0
- better_notion/plugins/official/agents_sdk/history.py +658 -0
- better_notion/plugins/official/agents_sdk/managers.py +409 -0
- better_notion/plugins/official/agents_sdk/search.py +421 -0
- {better_notion-2.2.0.dist-info → better_notion-2.3.1.dist-info}/METADATA +1 -1
- {better_notion-2.2.0.dist-info → better_notion-2.3.1.dist-info}/RECORD +11 -8
- {better_notion-2.2.0.dist-info → better_notion-2.3.1.dist-info}/WHEEL +0 -0
- {better_notion-2.2.0.dist-info → better_notion-2.3.1.dist-info}/entry_points.txt +0 -0
- {better_notion-2.2.0.dist-info → better_notion-2.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -769,6 +769,235 @@ class TaskManager:
|
|
|
769
769
|
"""
|
|
770
770
|
return await self.find_ready(version_id)
|
|
771
771
|
|
|
772
|
+
async def search(
|
|
773
|
+
self,
|
|
774
|
+
query: str,
|
|
775
|
+
status: str | None = None,
|
|
776
|
+
priority: str | None = None,
|
|
777
|
+
assignee: str | None = None,
|
|
778
|
+
project_id: str | None = None,
|
|
779
|
+
version_id: str | None = None,
|
|
780
|
+
limit: int = 50,
|
|
781
|
+
) -> list[dict]:
|
|
782
|
+
"""Search tasks by text query with optional filters.
|
|
783
|
+
|
|
784
|
+
Args:
|
|
785
|
+
query: Text search query
|
|
786
|
+
status: Optional status filter
|
|
787
|
+
priority: Optional priority filter
|
|
788
|
+
assignee: Optional assignee filter
|
|
789
|
+
project_id: Optional project ID filter
|
|
790
|
+
version_id: Optional version ID filter
|
|
791
|
+
limit: Maximum results to return
|
|
792
|
+
|
|
793
|
+
Returns:
|
|
794
|
+
List of search result dictionaries
|
|
795
|
+
"""
|
|
796
|
+
from better_notion.plugins.official.agents_sdk.search import TextSearcher
|
|
797
|
+
|
|
798
|
+
database_id = self._get_database_id("Tasks")
|
|
799
|
+
if not database_id:
|
|
800
|
+
return []
|
|
801
|
+
|
|
802
|
+
# Build filters
|
|
803
|
+
filters: list[dict[str, Any]] = []
|
|
804
|
+
|
|
805
|
+
if status:
|
|
806
|
+
filters.append({"property": "Status", "select": {"equals": status}})
|
|
807
|
+
|
|
808
|
+
if priority:
|
|
809
|
+
filters.append({"property": "Priority", "select": {"equals": priority}})
|
|
810
|
+
|
|
811
|
+
if assignee:
|
|
812
|
+
filters.append({"property": "Assignee", "select": {"equals": assignee}})
|
|
813
|
+
|
|
814
|
+
# Combine filters
|
|
815
|
+
filter_dict = None
|
|
816
|
+
if filters:
|
|
817
|
+
if len(filters) == 1:
|
|
818
|
+
filter_dict = filters[0]
|
|
819
|
+
else:
|
|
820
|
+
filter_dict = {"and": filters}
|
|
821
|
+
|
|
822
|
+
searcher = TextSearcher(self._client)
|
|
823
|
+
results = await searcher.search_tasks(query, database_id, filter_dict, limit)
|
|
824
|
+
|
|
825
|
+
# Filter by project/version if specified (post-filter due to relation filtering)
|
|
826
|
+
if project_id or version_id:
|
|
827
|
+
filtered_results = []
|
|
828
|
+
for result in results:
|
|
829
|
+
task = result.entity
|
|
830
|
+
# Filter by version
|
|
831
|
+
if version_id:
|
|
832
|
+
task_version = getattr(task, "version_id", None)
|
|
833
|
+
if task_version != version_id:
|
|
834
|
+
continue
|
|
835
|
+
|
|
836
|
+
# Filter by project
|
|
837
|
+
if project_id:
|
|
838
|
+
version = await task.version() if hasattr(task, "version") else None
|
|
839
|
+
if version:
|
|
840
|
+
project = await version.project() if hasattr(version, "project") else None
|
|
841
|
+
if project and project.id != project_id:
|
|
842
|
+
continue
|
|
843
|
+
|
|
844
|
+
filtered_results.append(result)
|
|
845
|
+
|
|
846
|
+
results = filtered_results
|
|
847
|
+
|
|
848
|
+
return [r.to_dict() for r in results]
|
|
849
|
+
|
|
850
|
+
async def pick(
|
|
851
|
+
self,
|
|
852
|
+
skills: list[str] | None = None,
|
|
853
|
+
max_priority: str | None = None,
|
|
854
|
+
exclude_patterns: list[str] | None = None,
|
|
855
|
+
count: int = 5,
|
|
856
|
+
project_id: str | None = None,
|
|
857
|
+
version_id: str | None = None,
|
|
858
|
+
) -> list[dict]:
|
|
859
|
+
"""Pick the best tasks for an agent to work on.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
skills: List of agent skills (e.g., ["python", "database"])
|
|
863
|
+
max_priority: Maximum priority to consider
|
|
864
|
+
exclude_patterns: Regex patterns to exclude
|
|
865
|
+
count: Maximum number of recommendations
|
|
866
|
+
project_id: Optional project filter
|
|
867
|
+
version_id: Optional version filter
|
|
868
|
+
|
|
869
|
+
Returns:
|
|
870
|
+
List of task recommendation dictionaries
|
|
871
|
+
"""
|
|
872
|
+
from better_notion.plugins.official.agents_sdk.agent import TaskSelector
|
|
873
|
+
|
|
874
|
+
selector = TaskSelector(self._client)
|
|
875
|
+
recommendations = await selector.pick_best_tasks(
|
|
876
|
+
skills=skills,
|
|
877
|
+
max_priority=max_priority,
|
|
878
|
+
exclude_patterns=exclude_patterns,
|
|
879
|
+
count=count,
|
|
880
|
+
project_id=project_id,
|
|
881
|
+
version_id=version_id,
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
return [r.to_dict() for r in recommendations]
|
|
885
|
+
|
|
886
|
+
async def suggest(
|
|
887
|
+
self,
|
|
888
|
+
unassigned: bool = False,
|
|
889
|
+
priority: list[str] | None = None,
|
|
890
|
+
count: int = 5,
|
|
891
|
+
) -> list[dict]:
|
|
892
|
+
"""Suggest tasks based on criteria.
|
|
893
|
+
|
|
894
|
+
Args:
|
|
895
|
+
unassigned: Only suggest unassigned tasks
|
|
896
|
+
priority: List of priority levels to include
|
|
897
|
+
count: Maximum number of suggestions
|
|
898
|
+
|
|
899
|
+
Returns:
|
|
900
|
+
List of task dictionaries
|
|
901
|
+
"""
|
|
902
|
+
# Get candidate tasks
|
|
903
|
+
filters = []
|
|
904
|
+
|
|
905
|
+
if unassigned:
|
|
906
|
+
filters.append({"property": "Assignee", "select": {"is_empty": True}})
|
|
907
|
+
|
|
908
|
+
if priority:
|
|
909
|
+
or_filter = {"or": [{"property": "Priority", "select": {"equals": p}} for p in priority]}
|
|
910
|
+
filters.append(or_filter)
|
|
911
|
+
|
|
912
|
+
# Query with filters
|
|
913
|
+
database_id = self._get_database_id("Tasks")
|
|
914
|
+
if not database_id:
|
|
915
|
+
return []
|
|
916
|
+
|
|
917
|
+
query_obj: dict[str, Any] = {}
|
|
918
|
+
if filters:
|
|
919
|
+
if len(filters) == 1:
|
|
920
|
+
query_obj["filter"] = filters[0]
|
|
921
|
+
else:
|
|
922
|
+
query_obj["filter"] = {"and": filters}
|
|
923
|
+
|
|
924
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
925
|
+
|
|
926
|
+
response = await self._client._api._request(
|
|
927
|
+
"POST",
|
|
928
|
+
f"/databases/{database_id}/query",
|
|
929
|
+
json=query_obj,
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
tasks = [Task(self._client, page_data) for page_data in response.get("results", [])]
|
|
933
|
+
|
|
934
|
+
# Sort by priority (Critical > High > Medium > Low)
|
|
935
|
+
priority_order = {"Critical": 0, "High": 1, "Medium": 2, "Low": 3}
|
|
936
|
+
tasks.sort(key=lambda t: priority_order.get(getattr(t, "priority", "Low"), 3))
|
|
937
|
+
|
|
938
|
+
# Get top N and convert to dicts
|
|
939
|
+
suggestions = []
|
|
940
|
+
for task in tasks[:count]:
|
|
941
|
+
suggestions.append({
|
|
942
|
+
"id": task.id,
|
|
943
|
+
"title": task.title,
|
|
944
|
+
"priority": getattr(task, "priority", None),
|
|
945
|
+
"status": getattr(task, "status", None),
|
|
946
|
+
"assignee": getattr(task, "assignee", None),
|
|
947
|
+
"type": getattr(task, "task_type", None),
|
|
948
|
+
})
|
|
949
|
+
|
|
950
|
+
return suggestions
|
|
951
|
+
|
|
952
|
+
async def claim(self, task_id: str) -> dict:
|
|
953
|
+
"""Claim a task (set status to In Progress).
|
|
954
|
+
|
|
955
|
+
Args:
|
|
956
|
+
task_id: Task ID to claim
|
|
957
|
+
|
|
958
|
+
Returns:
|
|
959
|
+
Dict with task_id and new status
|
|
960
|
+
"""
|
|
961
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
962
|
+
|
|
963
|
+
task = await Task.get(task_id, client=self._client)
|
|
964
|
+
await task.update(status="In Progress")
|
|
965
|
+
|
|
966
|
+
return {
|
|
967
|
+
"task_id": task_id,
|
|
968
|
+
"status": "In Progress",
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
async def start(self, task_id: str) -> dict:
|
|
972
|
+
"""Start a task (alias for claim).
|
|
973
|
+
|
|
974
|
+
Args:
|
|
975
|
+
task_id: Task ID to start
|
|
976
|
+
|
|
977
|
+
Returns:
|
|
978
|
+
Dict with task_id and new status
|
|
979
|
+
"""
|
|
980
|
+
return await self.claim(task_id)
|
|
981
|
+
|
|
982
|
+
async def complete(self, task_id: str) -> dict:
|
|
983
|
+
"""Complete a task (set status to Completed).
|
|
984
|
+
|
|
985
|
+
Args:
|
|
986
|
+
task_id: Task ID to complete
|
|
987
|
+
|
|
988
|
+
Returns:
|
|
989
|
+
Dict with task_id and new status
|
|
990
|
+
"""
|
|
991
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
992
|
+
|
|
993
|
+
task = await Task.get(task_id, client=self._client)
|
|
994
|
+
await task.update(status="Completed")
|
|
995
|
+
|
|
996
|
+
return {
|
|
997
|
+
"task_id": task_id,
|
|
998
|
+
"status": "Completed",
|
|
999
|
+
}
|
|
1000
|
+
|
|
772
1001
|
def _get_database_id(self, name: str) -> str | None:
|
|
773
1002
|
"""Get database ID from workspace config."""
|
|
774
1003
|
return getattr(self._client, "_workspace_config", {}).get(name)
|
|
@@ -890,6 +1119,57 @@ class IdeaManager:
|
|
|
890
1119
|
ideas = await self.list(status="Accepted")
|
|
891
1120
|
return [idea for idea in ideas if not idea.related_task_id]
|
|
892
1121
|
|
|
1122
|
+
async def search(
|
|
1123
|
+
self,
|
|
1124
|
+
query: str,
|
|
1125
|
+
status: str | None = None,
|
|
1126
|
+
category: str | None = None,
|
|
1127
|
+
project_id: str | None = None,
|
|
1128
|
+
limit: int = 50,
|
|
1129
|
+
) -> list[dict]:
|
|
1130
|
+
"""Search ideas by text query with optional filters.
|
|
1131
|
+
|
|
1132
|
+
Args:
|
|
1133
|
+
query: Text search query
|
|
1134
|
+
status: Optional status filter
|
|
1135
|
+
category: Optional category filter
|
|
1136
|
+
project_id: Optional project ID filter
|
|
1137
|
+
limit: Maximum results to return
|
|
1138
|
+
|
|
1139
|
+
Returns:
|
|
1140
|
+
List of search result dictionaries
|
|
1141
|
+
"""
|
|
1142
|
+
from better_notion.plugins.official.agents_sdk.search import TextSearcher
|
|
1143
|
+
|
|
1144
|
+
database_id = self._get_database_id("Ideas")
|
|
1145
|
+
if not database_id:
|
|
1146
|
+
return []
|
|
1147
|
+
|
|
1148
|
+
# Build filters
|
|
1149
|
+
filters: list[dict[str, Any]] = []
|
|
1150
|
+
|
|
1151
|
+
if status:
|
|
1152
|
+
filters.append({"property": "Status", "select": {"equals": status}})
|
|
1153
|
+
|
|
1154
|
+
if category:
|
|
1155
|
+
filters.append({"property": "Category", "select": {"equals": category}})
|
|
1156
|
+
|
|
1157
|
+
if project_id:
|
|
1158
|
+
filters.append({"property": "Project", "relation": {"contains": project_id}})
|
|
1159
|
+
|
|
1160
|
+
# Combine filters
|
|
1161
|
+
filter_dict = None
|
|
1162
|
+
if filters:
|
|
1163
|
+
if len(filters) == 1:
|
|
1164
|
+
filter_dict = filters[0]
|
|
1165
|
+
else:
|
|
1166
|
+
filter_dict = {"and": filters}
|
|
1167
|
+
|
|
1168
|
+
searcher = TextSearcher(self._client)
|
|
1169
|
+
results = await searcher.search_ideas(query, database_id, filter_dict, limit)
|
|
1170
|
+
|
|
1171
|
+
return [r.to_dict() for r in results]
|
|
1172
|
+
|
|
893
1173
|
def _get_database_id(self, name: str) -> str | None:
|
|
894
1174
|
"""Get database ID from workspace config."""
|
|
895
1175
|
return getattr(self._client, "_workspace_config", {}).get(name)
|
|
@@ -1105,6 +1385,62 @@ class WorkIssueManager:
|
|
|
1105
1385
|
for page_data in response.get("results", [])
|
|
1106
1386
|
]
|
|
1107
1387
|
|
|
1388
|
+
async def search(
|
|
1389
|
+
self,
|
|
1390
|
+
query: str,
|
|
1391
|
+
status: str | None = None,
|
|
1392
|
+
type_: str | None = None,
|
|
1393
|
+
severity: str | None = None,
|
|
1394
|
+
project_id: str | None = None,
|
|
1395
|
+
limit: int = 50,
|
|
1396
|
+
) -> list[dict]:
|
|
1397
|
+
"""Search work issues by text query with optional filters.
|
|
1398
|
+
|
|
1399
|
+
Args:
|
|
1400
|
+
query: Text search query
|
|
1401
|
+
status: Optional status filter
|
|
1402
|
+
type_: Optional type filter
|
|
1403
|
+
severity: Optional severity filter
|
|
1404
|
+
project_id: Optional project ID filter
|
|
1405
|
+
limit: Maximum results to return
|
|
1406
|
+
|
|
1407
|
+
Returns:
|
|
1408
|
+
List of search result dictionaries
|
|
1409
|
+
"""
|
|
1410
|
+
from better_notion.plugins.official.agents_sdk.search import TextSearcher
|
|
1411
|
+
|
|
1412
|
+
database_id = self._get_database_id("Work_issues")
|
|
1413
|
+
if not database_id:
|
|
1414
|
+
return []
|
|
1415
|
+
|
|
1416
|
+
# Build filters
|
|
1417
|
+
filters: list[dict[str, Any]] = []
|
|
1418
|
+
|
|
1419
|
+
if status:
|
|
1420
|
+
filters.append({"property": "Status", "select": {"equals": status}})
|
|
1421
|
+
|
|
1422
|
+
if type_:
|
|
1423
|
+
filters.append({"property": "Type", "select": {"equals": type_}})
|
|
1424
|
+
|
|
1425
|
+
if severity:
|
|
1426
|
+
filters.append({"property": "Severity", "select": {"equals": severity}})
|
|
1427
|
+
|
|
1428
|
+
if project_id:
|
|
1429
|
+
filters.append({"property": "Project", "relation": {"contains": project_id}})
|
|
1430
|
+
|
|
1431
|
+
# Combine filters
|
|
1432
|
+
filter_dict = None
|
|
1433
|
+
if filters:
|
|
1434
|
+
if len(filters) == 1:
|
|
1435
|
+
filter_dict = filters[0]
|
|
1436
|
+
else:
|
|
1437
|
+
filter_dict = {"and": filters}
|
|
1438
|
+
|
|
1439
|
+
searcher = TextSearcher(self._client)
|
|
1440
|
+
results = await searcher.search_work_issues(query, database_id, filter_dict, limit)
|
|
1441
|
+
|
|
1442
|
+
return [r.to_dict() for r in results]
|
|
1443
|
+
|
|
1108
1444
|
def _get_database_id(self, name: str) -> str | None:
|
|
1109
1445
|
"""Get database ID from workspace config."""
|
|
1110
1446
|
return getattr(self._client, "_workspace_config", {}).get(name)
|
|
@@ -1198,6 +1534,79 @@ class IncidentManager:
|
|
|
1198
1534
|
|
|
1199
1535
|
return incidents
|
|
1200
1536
|
|
|
1537
|
+
async def search(
|
|
1538
|
+
self,
|
|
1539
|
+
query: str,
|
|
1540
|
+
status: str | None = None,
|
|
1541
|
+
severity: str | None = None,
|
|
1542
|
+
incident_type: str | None = None,
|
|
1543
|
+
project_id: str | None = None,
|
|
1544
|
+
limit: int = 50,
|
|
1545
|
+
) -> list[dict]:
|
|
1546
|
+
"""Search incidents by text query with optional filters.
|
|
1547
|
+
|
|
1548
|
+
Args:
|
|
1549
|
+
query: Text search query
|
|
1550
|
+
status: Optional status filter
|
|
1551
|
+
severity: Optional severity filter
|
|
1552
|
+
incident_type: Optional incident type filter
|
|
1553
|
+
project_id: Optional project ID filter
|
|
1554
|
+
limit: Maximum results to return
|
|
1555
|
+
|
|
1556
|
+
Returns:
|
|
1557
|
+
List of search result dictionaries
|
|
1558
|
+
"""
|
|
1559
|
+
from better_notion.plugins.official.agents_sdk.search import TextSearcher
|
|
1560
|
+
|
|
1561
|
+
database_id = self._get_database_id("Incidents")
|
|
1562
|
+
if not database_id:
|
|
1563
|
+
return []
|
|
1564
|
+
|
|
1565
|
+
# Build filters
|
|
1566
|
+
filters: list[dict[str, Any]] = []
|
|
1567
|
+
|
|
1568
|
+
if status:
|
|
1569
|
+
filters.append({"property": "Status", "select": {"equals": status}})
|
|
1570
|
+
|
|
1571
|
+
if severity:
|
|
1572
|
+
filters.append({"property": "Severity", "select": {"equals": severity}})
|
|
1573
|
+
|
|
1574
|
+
if incident_type:
|
|
1575
|
+
filters.append({"property": "Type", "select": {"equals": incident_type}})
|
|
1576
|
+
|
|
1577
|
+
if project_id:
|
|
1578
|
+
filters.append({"property": "Project", "relation": {"contains": project_id}})
|
|
1579
|
+
|
|
1580
|
+
# Combine filters
|
|
1581
|
+
filter_dict = None
|
|
1582
|
+
if filters:
|
|
1583
|
+
if len(filters) == 1:
|
|
1584
|
+
filter_dict = filters[0]
|
|
1585
|
+
else:
|
|
1586
|
+
filter_dict = {"and": filters}
|
|
1587
|
+
|
|
1588
|
+
searcher = TextSearcher(self._client)
|
|
1589
|
+
results = await searcher.search_incidents(query, database_id, filter_dict, limit)
|
|
1590
|
+
|
|
1591
|
+
return [r.to_dict() for r in results]
|
|
1592
|
+
|
|
1593
|
+
async def triage(self, incident_id: str) -> dict:
|
|
1594
|
+
"""Triage and classify an incident.
|
|
1595
|
+
|
|
1596
|
+
Args:
|
|
1597
|
+
incident_id: Incident ID to triage
|
|
1598
|
+
|
|
1599
|
+
Returns:
|
|
1600
|
+
Dictionary with triage results including severity, type, and suggested assignment
|
|
1601
|
+
"""
|
|
1602
|
+
from better_notion.plugins.official.agents_sdk.agent import IncidentTriager
|
|
1603
|
+
from better_notion.plugins.official.agents_sdk.models import Incident
|
|
1604
|
+
|
|
1605
|
+
incident = await Incident.get(incident_id, client=self._client)
|
|
1606
|
+
triager = IncidentTriager(self._client)
|
|
1607
|
+
|
|
1608
|
+
return await triager.triage_incident(incident)
|
|
1609
|
+
|
|
1201
1610
|
async def find_sla_violations(self) -> list:
|
|
1202
1611
|
"""
|
|
1203
1612
|
Find all incidents that violated SLA.
|