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
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
"""AI-aware agent workflow utilities for the agents SDK.
|
|
2
|
+
|
|
3
|
+
This module provides intelligent task selection, incident triage, and batch
|
|
4
|
+
operations optimized for AI agent workflows.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from better_notion._sdk.client import NotionClient
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class TaskRecommendation:
|
|
19
|
+
"""Represents a task recommendation for an agent.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
task: The Task entity
|
|
23
|
+
match_score: Score from 0-100 indicating how well the task matches
|
|
24
|
+
match_reason: Human-readable explanation of why this task was recommended
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
task: Any
|
|
28
|
+
match_score: float
|
|
29
|
+
match_reason: str
|
|
30
|
+
|
|
31
|
+
def to_dict(self) -> dict[str, Any]:
|
|
32
|
+
"""Convert recommendation to dictionary.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dictionary with task details and recommendation metadata
|
|
36
|
+
"""
|
|
37
|
+
return {
|
|
38
|
+
"id": self.task.id,
|
|
39
|
+
"title": self.task.title,
|
|
40
|
+
"priority": getattr(self.task, "priority", None),
|
|
41
|
+
"status": getattr(self.task, "status", None),
|
|
42
|
+
"match_score": self.match_score,
|
|
43
|
+
"match_reason": self.match_reason,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TaskSelector:
|
|
48
|
+
"""Intelligent task selection for AI agents.
|
|
49
|
+
|
|
50
|
+
Provides scoring algorithms to pick the best tasks for an agent to work on
|
|
51
|
+
based on skills, priority, age, and other factors.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, client: NotionClient) -> None:
|
|
55
|
+
"""Initialize the TaskSelector.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
client: Notion API client
|
|
59
|
+
"""
|
|
60
|
+
self._client = client
|
|
61
|
+
|
|
62
|
+
async def pick_best_tasks(
|
|
63
|
+
self,
|
|
64
|
+
skills: list[str] | None = None,
|
|
65
|
+
max_priority: str | None = None,
|
|
66
|
+
exclude_patterns: list[str] | None = None,
|
|
67
|
+
count: int = 5,
|
|
68
|
+
project_id: str | None = None,
|
|
69
|
+
version_id: str | None = None,
|
|
70
|
+
) -> list[TaskRecommendation]:
|
|
71
|
+
"""Pick the best tasks for an agent to work on.
|
|
72
|
+
|
|
73
|
+
Tasks are scored based on:
|
|
74
|
+
- Priority (0-40 points): Critical > High > Medium > Low
|
|
75
|
+
- Skills (0-30 points): Matching skills in title/description
|
|
76
|
+
- Age (0-20 points): Older tasks get higher priority
|
|
77
|
+
- Ready bonus (10 points): No blocking dependencies
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
skills: List of skills the agent has (e.g., ["python", "database"])
|
|
81
|
+
max_priority: Maximum priority level to consider (e.g., "High")
|
|
82
|
+
exclude_patterns: Regex patterns to exclude from task titles
|
|
83
|
+
count: Maximum number of recommendations to return
|
|
84
|
+
project_id: Filter to specific project
|
|
85
|
+
version_id: Filter to specific version
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
List of task recommendations sorted by match score (descending)
|
|
89
|
+
"""
|
|
90
|
+
from better_notion.plugins.official.agents_sdk.managers import TaskManager
|
|
91
|
+
|
|
92
|
+
task_mgr = TaskManager(self._client, getattr(self._client, "_workspace_config", {}))
|
|
93
|
+
|
|
94
|
+
# Get candidate tasks - filter to backlog only
|
|
95
|
+
candidates = await task_mgr.list(
|
|
96
|
+
status="Backlog",
|
|
97
|
+
project_id=project_id,
|
|
98
|
+
version_id=version_id,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Filter and score candidates
|
|
102
|
+
recommendations = []
|
|
103
|
+
|
|
104
|
+
for task in candidates:
|
|
105
|
+
# Apply filters
|
|
106
|
+
if max_priority:
|
|
107
|
+
priority_order = {"Critical": 4, "High": 3, "Medium": 2, "Low": 1}
|
|
108
|
+
task_prio = getattr(task, "priority", "Low")
|
|
109
|
+
if priority_order.get(task_prio, 0) > priority_order.get(max_priority, 0):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
if exclude_patterns:
|
|
113
|
+
title_lower = task.title.lower()
|
|
114
|
+
if any(re.search(pattern, title_lower) for pattern in exclude_patterns):
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
# Calculate match score
|
|
118
|
+
score, reason = self._calculate_score(task, skills)
|
|
119
|
+
|
|
120
|
+
recommendations.append(
|
|
121
|
+
TaskRecommendation(
|
|
122
|
+
task=task,
|
|
123
|
+
match_score=score,
|
|
124
|
+
match_reason=reason,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Sort by score (descending)
|
|
129
|
+
recommendations.sort(key=lambda r: r.match_score, reverse=True)
|
|
130
|
+
|
|
131
|
+
return recommendations[:count]
|
|
132
|
+
|
|
133
|
+
def _calculate_score(
|
|
134
|
+
self,
|
|
135
|
+
task: Any,
|
|
136
|
+
skills: list[str] | None,
|
|
137
|
+
) -> tuple[float, str]:
|
|
138
|
+
"""Calculate a match score for a task.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
task: The task entity to score
|
|
142
|
+
skills: List of agent's skills
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Tuple of (score, reason) where score is 0-100 and reason explains the score
|
|
146
|
+
"""
|
|
147
|
+
score = 0.0
|
|
148
|
+
reasons = []
|
|
149
|
+
|
|
150
|
+
# Priority scoring (0-40 points)
|
|
151
|
+
priority_scores = {
|
|
152
|
+
"Critical": 40,
|
|
153
|
+
"High": 30,
|
|
154
|
+
"Medium": 20,
|
|
155
|
+
"Low": 10,
|
|
156
|
+
}
|
|
157
|
+
task_prio = getattr(task, "priority", "Low")
|
|
158
|
+
prio_score = priority_scores.get(task_prio, 0)
|
|
159
|
+
score += prio_score
|
|
160
|
+
if prio_score > 0:
|
|
161
|
+
reasons.append(f"{task_prio} priority")
|
|
162
|
+
|
|
163
|
+
# Skill matching (0-30 points)
|
|
164
|
+
if skills:
|
|
165
|
+
task_title_lower = task.title.lower()
|
|
166
|
+
task_desc_lower = getattr(task, "description", "") or ""
|
|
167
|
+
if isinstance(task_desc_lower, str):
|
|
168
|
+
task_desc_lower = task_desc_lower.lower()
|
|
169
|
+
else:
|
|
170
|
+
task_desc_lower = ""
|
|
171
|
+
|
|
172
|
+
matched_skills = []
|
|
173
|
+
for skill in skills:
|
|
174
|
+
skill_lower = skill.lower()
|
|
175
|
+
if skill_lower in task_title_lower or skill_lower in task_desc_lower:
|
|
176
|
+
matched_skills.append(skill)
|
|
177
|
+
|
|
178
|
+
if matched_skills:
|
|
179
|
+
skill_points = min(30, len(matched_skills) * 10)
|
|
180
|
+
score += skill_points
|
|
181
|
+
reasons.append(f"matches skills: {', '.join(matched_skills)}")
|
|
182
|
+
|
|
183
|
+
# Age scoring (0-20 points) - older tasks get higher priority
|
|
184
|
+
# Note: Not implemented in v1 due to limited created_at access
|
|
185
|
+
# if hasattr(task, 'created_at') and task.created_at:
|
|
186
|
+
# days_old = (datetime.now(timezone.utc) - task.created_at).days
|
|
187
|
+
# age_points = min(20, days_old)
|
|
188
|
+
# score += age_points
|
|
189
|
+
# if age_points > 0:
|
|
190
|
+
# reasons.append(f"{days_old} days old")
|
|
191
|
+
|
|
192
|
+
# Bonus for ready tasks (no blockers) - 10 points
|
|
193
|
+
# Note: Not implemented in v1 - requires dependency resolution from issue #040
|
|
194
|
+
# if hasattr(task, 'can_start') and await task.can_start():
|
|
195
|
+
# score += 10
|
|
196
|
+
# reasons.append("no blocking dependencies")
|
|
197
|
+
|
|
198
|
+
reason = "; ".join(reasons) if reasons else "no specific match criteria"
|
|
199
|
+
return score, reason
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class IncidentTriager:
|
|
203
|
+
"""Automatic incident triage and classification.
|
|
204
|
+
|
|
205
|
+
Uses keyword-based heuristics to classify incidents by severity and type,
|
|
206
|
+
suggest team assignments, and provide confidence scores.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
# Severity classification keywords
|
|
210
|
+
CRITICAL_KEYWORDS = ["down", "outage", "critical", "emergency", "production down", "crash"]
|
|
211
|
+
HIGH_KEYWORDS = ["slow", "degraded", "error", "bug", "broken", "failure"]
|
|
212
|
+
MEDIUM_KEYWORDS = ["issue", "problem", "glitch"]
|
|
213
|
+
|
|
214
|
+
# Type classification keywords
|
|
215
|
+
TYPE_KEYWORDS: dict[str, list[str]] = {
|
|
216
|
+
"Performance": ["slow", "latency", "performance", "timeout", "lag"],
|
|
217
|
+
"Security": ["security", "auth", "unauthorized", "vulnerability", "injection", "exploit"],
|
|
218
|
+
"Bug": ["bug", "error", "broken", "crash", "exception", "fault"],
|
|
219
|
+
"Service Disruption": ["down", "outage", "unavailable", "can't access", "500"],
|
|
220
|
+
"Data": ["data", "database", "corruption", "loss", "leak", "inconsistent"],
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# Team assignment suggestions
|
|
224
|
+
TEAM_MAPPING: dict[str, str] = {
|
|
225
|
+
"Performance": "Performance Team",
|
|
226
|
+
"Security": "Security Team",
|
|
227
|
+
"Bug": "Engineering Team",
|
|
228
|
+
"Service Disruption": "Operations Team",
|
|
229
|
+
"Data": "Data Team",
|
|
230
|
+
"General": "Engineering Team",
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Keywords for confidence calculation
|
|
234
|
+
SPECIFIC_KEYWORDS = [
|
|
235
|
+
"down",
|
|
236
|
+
"outage",
|
|
237
|
+
"slow",
|
|
238
|
+
"security",
|
|
239
|
+
"bug",
|
|
240
|
+
"performance",
|
|
241
|
+
"database",
|
|
242
|
+
"api",
|
|
243
|
+
"critical",
|
|
244
|
+
"crash",
|
|
245
|
+
"error",
|
|
246
|
+
"timeout",
|
|
247
|
+
"latency",
|
|
248
|
+
"unauthorized",
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
def __init__(self, client: NotionClient) -> None:
|
|
252
|
+
"""Initialize the IncidentTriager.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
client: Notion API client
|
|
256
|
+
"""
|
|
257
|
+
self._client = client
|
|
258
|
+
|
|
259
|
+
async def triage_incident(self, incident: Any) -> dict[str, Any]:
|
|
260
|
+
"""Triage and classify an incident.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
incident: The Incident entity to triage
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Dictionary with classification results:
|
|
267
|
+
- severity: Classified severity level
|
|
268
|
+
- type: Incident type
|
|
269
|
+
- suggested_assignment: Team that should handle this
|
|
270
|
+
- confidence: Confidence score (0-1)
|
|
271
|
+
- reasoning: Explanation of the classification
|
|
272
|
+
"""
|
|
273
|
+
title_lower = incident.title.lower()
|
|
274
|
+
description = getattr(incident, "description", None) or ""
|
|
275
|
+
if isinstance(description, str):
|
|
276
|
+
description_lower = description.lower()
|
|
277
|
+
else:
|
|
278
|
+
description_lower = ""
|
|
279
|
+
text = f"{title_lower} {description_lower}"
|
|
280
|
+
|
|
281
|
+
# Classify severity
|
|
282
|
+
severity = self._classify_severity(text)
|
|
283
|
+
|
|
284
|
+
# Classify type
|
|
285
|
+
incident_type = self._classify_type(text)
|
|
286
|
+
|
|
287
|
+
# Suggest assignment
|
|
288
|
+
suggested_team = self._suggest_assignment(incident_type, severity)
|
|
289
|
+
|
|
290
|
+
# Calculate confidence
|
|
291
|
+
confidence = self._calculate_confidence(text)
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
"severity": severity,
|
|
295
|
+
"type": incident_type,
|
|
296
|
+
"suggested_assignment": suggested_team,
|
|
297
|
+
"confidence": confidence,
|
|
298
|
+
"reasoning": self._get_reasoning(text, severity, incident_type),
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
def _classify_severity(self, text: str) -> str:
|
|
302
|
+
"""Classify incident severity based on keywords.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
text: Lowercase text to analyze
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Severity level: Critical, High, Medium, or Low
|
|
309
|
+
"""
|
|
310
|
+
if any(kw in text for kw in self.CRITICAL_KEYWORDS):
|
|
311
|
+
return "Critical"
|
|
312
|
+
elif any(kw in text for kw in self.HIGH_KEYWORDS):
|
|
313
|
+
return "High"
|
|
314
|
+
elif any(kw in text for kw in self.MEDIUM_KEYWORDS):
|
|
315
|
+
return "Medium"
|
|
316
|
+
else:
|
|
317
|
+
return "Low"
|
|
318
|
+
|
|
319
|
+
def _classify_type(self, text: str) -> str:
|
|
320
|
+
"""Classify incident type based on keywords.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
text: Lowercase text to analyze
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Incident type (e.g., Performance, Security, Bug, etc.)
|
|
327
|
+
"""
|
|
328
|
+
for incident_type, keywords in self.TYPE_KEYWORDS.items():
|
|
329
|
+
if any(kw in text for kw in keywords):
|
|
330
|
+
return incident_type
|
|
331
|
+
|
|
332
|
+
return "General"
|
|
333
|
+
|
|
334
|
+
def _suggest_assignment(self, incident_type: str, severity: str) -> str:
|
|
335
|
+
"""Suggest team assignment based on type and severity.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
incident_type: The classified incident type
|
|
339
|
+
severity: The classified severity level
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Name of the team that should handle this incident
|
|
343
|
+
"""
|
|
344
|
+
return self.TEAM_MAPPING.get(incident_type, "Engineering Team")
|
|
345
|
+
|
|
346
|
+
def _calculate_confidence(self, text: str) -> float:
|
|
347
|
+
"""Calculate confidence in classification.
|
|
348
|
+
|
|
349
|
+
More specific keywords = higher confidence.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
text: Lowercase text to analyze
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Confidence score from 0.0 to 1.0
|
|
356
|
+
"""
|
|
357
|
+
matches = sum(1 for kw in self.SPECIFIC_KEYWORDS if kw in text)
|
|
358
|
+
|
|
359
|
+
# Base confidence 0.5, +0.1 for each matching keyword, max 0.95
|
|
360
|
+
confidence = min(0.95, 0.5 + (matches * 0.1))
|
|
361
|
+
|
|
362
|
+
return round(confidence, 2)
|
|
363
|
+
|
|
364
|
+
def _get_reasoning(self, text: str, severity: str, incident_type: str) -> str:
|
|
365
|
+
"""Get reasoning for classification.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
text: The analyzed text
|
|
369
|
+
severity: Classified severity
|
|
370
|
+
incident_type: Classified type
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Human-readable explanation of the classification
|
|
374
|
+
"""
|
|
375
|
+
# Find matching keywords for reasoning
|
|
376
|
+
matched_keywords = []
|
|
377
|
+
for kw in self.SPECIFIC_KEYWORDS:
|
|
378
|
+
if kw in text:
|
|
379
|
+
matched_keywords.append(kw)
|
|
380
|
+
|
|
381
|
+
if matched_keywords:
|
|
382
|
+
keywords_str = ", ".join(matched_keywords[:3]) # Show top 3
|
|
383
|
+
return (
|
|
384
|
+
f"Classified as '{severity}' severity and '{incident_type}' type "
|
|
385
|
+
f"based on keywords: {keywords_str}"
|
|
386
|
+
)
|
|
387
|
+
else:
|
|
388
|
+
return (
|
|
389
|
+
f"Classified as '{severity}' severity and '{incident_type}' type "
|
|
390
|
+
"(low confidence - no specific keywords found)"
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class BatchOperationManager:
|
|
395
|
+
"""Manager for executing batch operations.
|
|
396
|
+
|
|
397
|
+
Allows execution of multiple operations in sequence with error handling
|
|
398
|
+
and summary statistics.
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
def __init__(self, client: NotionClient) -> None:
|
|
402
|
+
"""Initialize the BatchOperationManager.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
client: Notion API client
|
|
406
|
+
"""
|
|
407
|
+
self._client = client
|
|
408
|
+
|
|
409
|
+
async def execute_batch(
|
|
410
|
+
self,
|
|
411
|
+
operations: list[dict[str, Any]],
|
|
412
|
+
continue_on_error: bool = False,
|
|
413
|
+
) -> dict[str, Any]:
|
|
414
|
+
"""Execute a batch of operations.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
operations: List of operation dicts with 'command' and 'args' keys
|
|
418
|
+
continue_on_error: If True, continue after errors; if False, stop on first error
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Dictionary with results and summary:
|
|
422
|
+
- results: List of operation results
|
|
423
|
+
- summary: Total, succeeded, and failed counts
|
|
424
|
+
"""
|
|
425
|
+
results = []
|
|
426
|
+
succeeded = 0
|
|
427
|
+
failed = 0
|
|
428
|
+
|
|
429
|
+
for i, op in enumerate(operations):
|
|
430
|
+
try:
|
|
431
|
+
result = await self._execute_operation(op)
|
|
432
|
+
results.append(
|
|
433
|
+
{
|
|
434
|
+
"operation": i + 1,
|
|
435
|
+
"status": "success",
|
|
436
|
+
"result": result,
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
succeeded += 1
|
|
440
|
+
except Exception as e:
|
|
441
|
+
results.append(
|
|
442
|
+
{
|
|
443
|
+
"operation": i + 1,
|
|
444
|
+
"status": "error",
|
|
445
|
+
"error": str(e),
|
|
446
|
+
}
|
|
447
|
+
)
|
|
448
|
+
failed += 1
|
|
449
|
+
|
|
450
|
+
if not continue_on_error:
|
|
451
|
+
break
|
|
452
|
+
|
|
453
|
+
return {
|
|
454
|
+
"results": results,
|
|
455
|
+
"summary": {
|
|
456
|
+
"total": len(operations),
|
|
457
|
+
"succeeded": succeeded,
|
|
458
|
+
"failed": failed,
|
|
459
|
+
},
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async def _execute_operation(self, operation: dict[str, Any]) -> Any:
|
|
463
|
+
"""Execute a single operation.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
operation: Dict with 'command' and 'args' keys
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Result from the operation
|
|
470
|
+
|
|
471
|
+
Raises:
|
|
472
|
+
ValueError: If command is unknown
|
|
473
|
+
"""
|
|
474
|
+
command = operation.get("command")
|
|
475
|
+
args = operation.get("args", {})
|
|
476
|
+
|
|
477
|
+
# Map commands to manager methods
|
|
478
|
+
if command == "tasks claim":
|
|
479
|
+
from better_notion.plugins.official.agents_sdk.managers import TaskManager
|
|
480
|
+
|
|
481
|
+
mgr = TaskManager(self._client, self._client.workspace_config)
|
|
482
|
+
return await mgr.claim(args["task_id"])
|
|
483
|
+
|
|
484
|
+
elif command == "tasks start":
|
|
485
|
+
from better_notion.plugins.official.agents_sdk.managers import TaskManager
|
|
486
|
+
|
|
487
|
+
mgr = TaskManager(self._client, self._client.workspace_config)
|
|
488
|
+
return await mgr.start(args["task_id"])
|
|
489
|
+
|
|
490
|
+
elif command == "tasks complete":
|
|
491
|
+
from better_notion.plugins.official.agents_sdk.managers import TaskManager
|
|
492
|
+
|
|
493
|
+
mgr = TaskManager(self._client, self._client.workspace_config)
|
|
494
|
+
return await mgr.complete(args["task_id"])
|
|
495
|
+
|
|
496
|
+
elif command == "tasks create":
|
|
497
|
+
from better_notion.plugins.official.agents_sdk.managers import TaskManager
|
|
498
|
+
|
|
499
|
+
mgr = TaskManager(self._client, self._client.workspace_config)
|
|
500
|
+
return await mgr.create(**args)
|
|
501
|
+
|
|
502
|
+
else:
|
|
503
|
+
raise ValueError(f"Unknown command: {command}")
|