better-notion 2.1.9__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 +419 -1
- better_notion/plugins/official/agents_sdk/search.py +421 -0
- {better_notion-2.1.9.dist-info → better_notion-2.3.1.dist-info}/METADATA +1 -1
- {better_notion-2.1.9.dist-info → better_notion-2.3.1.dist-info}/RECORD +11 -8
- {better_notion-2.1.9.dist-info → better_notion-2.3.1.dist-info}/WHEEL +0 -0
- {better_notion-2.1.9.dist-info → better_notion-2.3.1.dist-info}/entry_points.txt +0 -0
- {better_notion-2.1.9.dist-info → better_notion-2.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -341,10 +341,19 @@ class TaskManager:
|
|
|
341
341
|
"select": {"equals": status},
|
|
342
342
|
})
|
|
343
343
|
|
|
344
|
+
# Build query payload
|
|
345
|
+
query_payload = {}
|
|
346
|
+
if filters:
|
|
347
|
+
# Apply filter only if there are filters
|
|
348
|
+
if len(filters) == 1:
|
|
349
|
+
query_payload["filter"] = filters[0]
|
|
350
|
+
else:
|
|
351
|
+
query_payload["filter"] = {"and": filters}
|
|
352
|
+
|
|
344
353
|
# Query pages
|
|
345
354
|
response = await self._client._api.databases.query(
|
|
346
355
|
database_id=database_id,
|
|
347
|
-
|
|
356
|
+
**query_payload
|
|
348
357
|
)
|
|
349
358
|
|
|
350
359
|
return [
|
|
@@ -760,6 +769,235 @@ class TaskManager:
|
|
|
760
769
|
"""
|
|
761
770
|
return await self.find_ready(version_id)
|
|
762
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
|
+
|
|
763
1001
|
def _get_database_id(self, name: str) -> str | None:
|
|
764
1002
|
"""Get database ID from workspace config."""
|
|
765
1003
|
return getattr(self._client, "_workspace_config", {}).get(name)
|
|
@@ -881,6 +1119,57 @@ class IdeaManager:
|
|
|
881
1119
|
ideas = await self.list(status="Accepted")
|
|
882
1120
|
return [idea for idea in ideas if not idea.related_task_id]
|
|
883
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
|
+
|
|
884
1173
|
def _get_database_id(self, name: str) -> str | None:
|
|
885
1174
|
"""Get database ID from workspace config."""
|
|
886
1175
|
return getattr(self._client, "_workspace_config", {}).get(name)
|
|
@@ -1096,6 +1385,62 @@ class WorkIssueManager:
|
|
|
1096
1385
|
for page_data in response.get("results", [])
|
|
1097
1386
|
]
|
|
1098
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
|
+
|
|
1099
1444
|
def _get_database_id(self, name: str) -> str | None:
|
|
1100
1445
|
"""Get database ID from workspace config."""
|
|
1101
1446
|
return getattr(self._client, "_workspace_config", {}).get(name)
|
|
@@ -1189,6 +1534,79 @@ class IncidentManager:
|
|
|
1189
1534
|
|
|
1190
1535
|
return incidents
|
|
1191
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
|
+
|
|
1192
1610
|
async def find_sla_violations(self) -> list:
|
|
1193
1611
|
"""
|
|
1194
1612
|
Find all incidents that violated SLA.
|