taskledger 0.1.0__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.
- taskledger/__init__.py +5 -0
- taskledger/__main__.py +6 -0
- taskledger/_version.py +24 -0
- taskledger/api/__init__.py +13 -0
- taskledger/api/handoff.py +247 -0
- taskledger/api/introductions.py +9 -0
- taskledger/api/locks.py +4 -0
- taskledger/api/plans.py +31 -0
- taskledger/api/project.py +185 -0
- taskledger/api/questions.py +19 -0
- taskledger/api/search.py +87 -0
- taskledger/api/task_runs.py +38 -0
- taskledger/api/tasks.py +61 -0
- taskledger/cli.py +600 -0
- taskledger/cli_actor.py +196 -0
- taskledger/cli_common.py +617 -0
- taskledger/cli_implement.py +409 -0
- taskledger/cli_migrate.py +328 -0
- taskledger/cli_misc.py +984 -0
- taskledger/cli_plan.py +478 -0
- taskledger/cli_question.py +350 -0
- taskledger/cli_task.py +257 -0
- taskledger/cli_validate.py +285 -0
- taskledger/command_inventory.py +125 -0
- taskledger/domain/__init__.py +2 -0
- taskledger/domain/models.py +1697 -0
- taskledger/domain/policies.py +542 -0
- taskledger/domain/states.py +320 -0
- taskledger/errors.py +165 -0
- taskledger/exchange.py +343 -0
- taskledger/ids.py +19 -0
- taskledger/py.typed +0 -0
- taskledger/search.py +349 -0
- taskledger/services/__init__.py +1 -0
- taskledger/services/actors.py +245 -0
- taskledger/services/dashboard.py +306 -0
- taskledger/services/doctor.py +435 -0
- taskledger/services/handoff.py +1029 -0
- taskledger/services/handoff_lifecycle.py +154 -0
- taskledger/services/navigation.py +930 -0
- taskledger/services/phase5_lock_transfer.py +96 -0
- taskledger/services/plan_lint.py +397 -0
- taskledger/services/serve_read_model.py +852 -0
- taskledger/services/tasks.py +4224 -0
- taskledger/services/validation.py +221 -0
- taskledger/services/web_dashboard.py +1742 -0
- taskledger/storage/__init__.py +39 -0
- taskledger/storage/atomic.py +57 -0
- taskledger/storage/common.py +90 -0
- taskledger/storage/events.py +98 -0
- taskledger/storage/frontmatter.py +57 -0
- taskledger/storage/indexes.py +42 -0
- taskledger/storage/init.py +187 -0
- taskledger/storage/locks.py +83 -0
- taskledger/storage/meta.py +103 -0
- taskledger/storage/migrations.py +207 -0
- taskledger/storage/paths.py +166 -0
- taskledger/storage/project_config.py +393 -0
- taskledger/storage/repos.py +256 -0
- taskledger/storage/task_store.py +836 -0
- taskledger/timeutils.py +7 -0
- taskledger-0.1.0.dist-info/METADATA +411 -0
- taskledger-0.1.0.dist-info/RECORD +67 -0
- taskledger-0.1.0.dist-info/WHEEL +5 -0
- taskledger-0.1.0.dist-info/entry_points.txt +2 -0
- taskledger-0.1.0.dist-info/licenses/LICENSE +201 -0
- taskledger-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal, cast
|
|
4
|
+
|
|
5
|
+
from taskledger.errors import LaunchError
|
|
6
|
+
|
|
7
|
+
TASKLEDGER_SCHEMA_VERSION = 1
|
|
8
|
+
TASKLEDGER_V2_FILE_VERSION = "v2"
|
|
9
|
+
TASKLEDGER_STORAGE_LAYOUT_VERSION = 2
|
|
10
|
+
TASKLEDGER_RECORD_SCHEMA_VERSION = TASKLEDGER_SCHEMA_VERSION
|
|
11
|
+
TASKLEDGER_TASK_FILE_VERSION = TASKLEDGER_V2_FILE_VERSION
|
|
12
|
+
OBJECT_TYPES = frozenset(
|
|
13
|
+
{
|
|
14
|
+
"task",
|
|
15
|
+
"plan",
|
|
16
|
+
"question",
|
|
17
|
+
"run",
|
|
18
|
+
"validation",
|
|
19
|
+
"change",
|
|
20
|
+
"intro",
|
|
21
|
+
"lock",
|
|
22
|
+
"event",
|
|
23
|
+
"todo",
|
|
24
|
+
"todos",
|
|
25
|
+
"link",
|
|
26
|
+
"links",
|
|
27
|
+
"requirement",
|
|
28
|
+
"requirements",
|
|
29
|
+
"handoff",
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
ActorType = Literal["agent", "user", "system"]
|
|
34
|
+
ActorRole = Literal["planner", "implementer", "validator", "reviewer", "operator"]
|
|
35
|
+
HarnessKind = Literal["agent_harness", "manual", "ci", "unknown"]
|
|
36
|
+
HandoffMode = Literal["planning", "implementation", "validation", "review", "full"]
|
|
37
|
+
ContextFor = Literal[
|
|
38
|
+
"planner",
|
|
39
|
+
"implementer",
|
|
40
|
+
"validator",
|
|
41
|
+
"reviewer",
|
|
42
|
+
"spec-reviewer",
|
|
43
|
+
"code-reviewer",
|
|
44
|
+
"full",
|
|
45
|
+
]
|
|
46
|
+
ContextScope = Literal["task", "todo", "run"]
|
|
47
|
+
ContextFormat = Literal["markdown", "json", "text"]
|
|
48
|
+
HandoffStatus = Literal["open", "claimed", "closed", "cancelled"]
|
|
49
|
+
LockPolicy = Literal["none", "retain", "release", "transfer"]
|
|
50
|
+
TodoSource = Literal["user", "planner", "implementer", "plan"]
|
|
51
|
+
|
|
52
|
+
TaskStatusStage = Literal[
|
|
53
|
+
"draft",
|
|
54
|
+
"planning",
|
|
55
|
+
"plan_review",
|
|
56
|
+
"approved",
|
|
57
|
+
"implementing",
|
|
58
|
+
"implemented",
|
|
59
|
+
"validating",
|
|
60
|
+
"done",
|
|
61
|
+
"failed_validation",
|
|
62
|
+
"cancelled",
|
|
63
|
+
]
|
|
64
|
+
ActiveTaskStatusStage = Literal["planning", "implementing", "validating"]
|
|
65
|
+
RunType = Literal["planning", "implementation", "validation"]
|
|
66
|
+
PlanStatus = Literal["draft", "proposed", "accepted", "superseded", "rejected"]
|
|
67
|
+
QuestionStatus = Literal["open", "answered", "dismissed"]
|
|
68
|
+
RunStatus = Literal[
|
|
69
|
+
"running", "paused", "finished", "passed", "failed", "blocked", "aborted"
|
|
70
|
+
]
|
|
71
|
+
ValidationResult = Literal["passed", "failed", "blocked"]
|
|
72
|
+
ValidationCheckStatus = Literal["pass", "fail", "warn", "not_run"]
|
|
73
|
+
FileLinkKind = Literal["code", "test", "doc", "config", "dir", "other", "artifact"]
|
|
74
|
+
TodoStatus = Literal["open", "active", "done", "blocked", "skipped"]
|
|
75
|
+
EventName = Literal[
|
|
76
|
+
"task.created",
|
|
77
|
+
"task.updated",
|
|
78
|
+
"task.cancelled",
|
|
79
|
+
"stage.entered",
|
|
80
|
+
"stage.completed",
|
|
81
|
+
"stage.failed",
|
|
82
|
+
"plan.started",
|
|
83
|
+
"plan.proposed",
|
|
84
|
+
"plan.approved",
|
|
85
|
+
"plan.rejected",
|
|
86
|
+
"question.added",
|
|
87
|
+
"question.answered",
|
|
88
|
+
"question.dismissed",
|
|
89
|
+
"implementation.started",
|
|
90
|
+
"implementation.logged",
|
|
91
|
+
"implementation.finished",
|
|
92
|
+
"validation.started",
|
|
93
|
+
"validation.finished",
|
|
94
|
+
"change.logged",
|
|
95
|
+
"todo.added",
|
|
96
|
+
"todo.toggled",
|
|
97
|
+
"todo.started",
|
|
98
|
+
"todo.blocked",
|
|
99
|
+
"todo.skipped",
|
|
100
|
+
"todo.completed",
|
|
101
|
+
"lock.acquired",
|
|
102
|
+
"lock.released",
|
|
103
|
+
"lock.broken",
|
|
104
|
+
"lock.transferred",
|
|
105
|
+
"handoff.created",
|
|
106
|
+
"handoff.claimed",
|
|
107
|
+
"handoff.closed",
|
|
108
|
+
"handoff.cancelled",
|
|
109
|
+
"run.paused",
|
|
110
|
+
"run.resumed",
|
|
111
|
+
"actor.resolved",
|
|
112
|
+
"doctor.reindexed",
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
ACTIVE_TASK_STAGES = frozenset({"planning", "implementing", "validating"})
|
|
116
|
+
DURABLE_TASK_STATUSES = frozenset(
|
|
117
|
+
{
|
|
118
|
+
"draft",
|
|
119
|
+
"plan_review",
|
|
120
|
+
"approved",
|
|
121
|
+
"implemented",
|
|
122
|
+
"failed_validation",
|
|
123
|
+
"done",
|
|
124
|
+
"cancelled",
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
RUN_TYPES = frozenset({"planning", "implementation", "validation"})
|
|
128
|
+
RUN_STATUSES = frozenset(
|
|
129
|
+
{"running", "paused", "finished", "passed", "failed", "blocked", "aborted"}
|
|
130
|
+
)
|
|
131
|
+
VALIDATION_CHECK_STATUSES = frozenset({"pass", "fail", "warn", "not_run"})
|
|
132
|
+
IMPLEMENTABLE_TASK_STAGES = frozenset({"approved", "failed_validation"})
|
|
133
|
+
CANCELLABLE_TASK_STAGES = frozenset(ACTIVE_TASK_STAGES) | {
|
|
134
|
+
"draft",
|
|
135
|
+
"plan_review",
|
|
136
|
+
"approved",
|
|
137
|
+
"implemented",
|
|
138
|
+
"failed_validation",
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
ALLOWED_STAGE_TRANSITIONS: dict[TaskStatusStage, frozenset[TaskStatusStage]] = {
|
|
142
|
+
"draft": frozenset({"plan_review", "cancelled"}),
|
|
143
|
+
"planning": frozenset({"plan_review", "cancelled"}),
|
|
144
|
+
"plan_review": frozenset({"draft", "approved", "cancelled"}),
|
|
145
|
+
"approved": frozenset({"implemented", "cancelled"}),
|
|
146
|
+
"implementing": frozenset({"implemented", "cancelled"}),
|
|
147
|
+
"implemented": frozenset({"done", "failed_validation", "cancelled"}),
|
|
148
|
+
"validating": frozenset({"done", "failed_validation", "cancelled"}),
|
|
149
|
+
"failed_validation": frozenset(
|
|
150
|
+
{"implementing", "approved", "plan_review", "cancelled"}
|
|
151
|
+
),
|
|
152
|
+
"done": frozenset(),
|
|
153
|
+
"cancelled": frozenset(),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
EXIT_CODE_SUCCESS = 0
|
|
157
|
+
EXIT_CODE_GENERIC_FAILURE = 1
|
|
158
|
+
EXIT_CODE_BAD_INPUT = 2
|
|
159
|
+
EXIT_CODE_WORKFLOW_REJECTION = 3
|
|
160
|
+
EXIT_CODE_LOCK_CONFLICT = 4
|
|
161
|
+
EXIT_CODE_MISSING = 5
|
|
162
|
+
EXIT_CODE_DATA_INTEGRITY = 6
|
|
163
|
+
EXIT_CODE_STORAGE_ERROR = 6
|
|
164
|
+
EXIT_CODE_VALIDATION_FAILED = 7
|
|
165
|
+
EXIT_CODE_INVALID_TRANSITION = EXIT_CODE_WORKFLOW_REJECTION
|
|
166
|
+
EXIT_CODE_APPROVAL_REQUIRED = EXIT_CODE_WORKFLOW_REJECTION
|
|
167
|
+
EXIT_CODE_DEPENDENCY_BLOCKED = EXIT_CODE_WORKFLOW_REJECTION
|
|
168
|
+
EXIT_CODE_STALE_LOCK_REQUIRES_BREAK = EXIT_CODE_LOCK_CONFLICT
|
|
169
|
+
EXIT_CODE_INDEX_REBUILD_FAILED = EXIT_CODE_STORAGE_ERROR
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def is_active_stage(stage: TaskStatusStage) -> bool:
|
|
173
|
+
return stage in ACTIVE_TASK_STAGES
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def can_transition(current: TaskStatusStage, target: TaskStatusStage) -> bool:
|
|
177
|
+
return target in ALLOWED_STAGE_TRANSITIONS[current]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def require_transition(current: TaskStatusStage, target: TaskStatusStage) -> None:
|
|
181
|
+
if can_transition(current, target):
|
|
182
|
+
return
|
|
183
|
+
raise LaunchError(f"Invalid stage transition: {current} -> {target}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def normalize_task_status_stage(value: str) -> TaskStatusStage:
|
|
187
|
+
if value not in ALLOWED_STAGE_TRANSITIONS:
|
|
188
|
+
raise LaunchError(f"Unsupported task stage: {value}")
|
|
189
|
+
return value
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def normalize_run_type(value: str) -> RunType:
|
|
193
|
+
if value not in {"planning", "implementation", "validation"}:
|
|
194
|
+
raise LaunchError(f"Unsupported run type: {value}")
|
|
195
|
+
return cast(RunType, value)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def normalize_plan_status(value: str) -> PlanStatus:
|
|
199
|
+
if value not in {"draft", "proposed", "accepted", "superseded", "rejected"}:
|
|
200
|
+
raise LaunchError(f"Unsupported plan status: {value}")
|
|
201
|
+
return cast(PlanStatus, value)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def normalize_question_status(value: str) -> QuestionStatus:
|
|
205
|
+
if value not in {"open", "answered", "dismissed"}:
|
|
206
|
+
raise LaunchError(f"Unsupported question status: {value}")
|
|
207
|
+
return cast(QuestionStatus, value)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def normalize_run_status(value: str) -> RunStatus:
|
|
211
|
+
if value not in {
|
|
212
|
+
"running",
|
|
213
|
+
"paused",
|
|
214
|
+
"finished",
|
|
215
|
+
"passed",
|
|
216
|
+
"failed",
|
|
217
|
+
"blocked",
|
|
218
|
+
"aborted",
|
|
219
|
+
}:
|
|
220
|
+
raise LaunchError(f"Unsupported run status: {value}")
|
|
221
|
+
return cast(RunStatus, value)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def normalize_validation_result(value: str) -> ValidationResult:
|
|
225
|
+
if value not in {"passed", "failed", "blocked"}:
|
|
226
|
+
raise LaunchError(f"Unsupported validation result: {value}")
|
|
227
|
+
return cast(ValidationResult, value)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def normalize_validation_check_status(value: str) -> ValidationCheckStatus:
|
|
231
|
+
normalized = "not_run" if value == "skip" else value
|
|
232
|
+
if normalized not in {"pass", "fail", "warn", "not_run"}:
|
|
233
|
+
raise LaunchError(f"Unsupported validation check status: {value}")
|
|
234
|
+
return cast(ValidationCheckStatus, normalized)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def normalize_file_link_kind(value: str) -> FileLinkKind:
|
|
238
|
+
normalized = "dir" if value == "directory" else value
|
|
239
|
+
if normalized not in {"code", "test", "doc", "config", "dir", "other", "artifact"}:
|
|
240
|
+
raise LaunchError(f"Unsupported file link kind: {value}")
|
|
241
|
+
return cast(FileLinkKind, normalized)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def normalize_todo_status(value: str) -> TodoStatus:
|
|
245
|
+
if value not in {"open", "active", "done", "blocked", "skipped"}:
|
|
246
|
+
raise LaunchError(f"Unsupported todo status: {value}")
|
|
247
|
+
return cast(TodoStatus, value)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
TODO_STATUSES = frozenset({"open", "active", "done", "blocked", "skipped"})
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def normalize_actor_type(value: str) -> ActorType:
|
|
254
|
+
if value not in {"agent", "user", "system"}:
|
|
255
|
+
raise LaunchError(f"Unsupported actor type: {value!r}")
|
|
256
|
+
return cast(ActorType, value)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def normalize_actor_role(value: str) -> ActorRole:
|
|
260
|
+
if value not in {"planner", "implementer", "validator", "reviewer", "operator"}:
|
|
261
|
+
raise LaunchError(f"Unsupported actor role: {value!r}")
|
|
262
|
+
return cast(ActorRole, value)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def normalize_harness_kind(value: str) -> HarnessKind:
|
|
266
|
+
if value not in {"agent_harness", "manual", "ci", "unknown"}:
|
|
267
|
+
raise LaunchError(f"Unsupported harness kind: {value!r}")
|
|
268
|
+
return cast(HarnessKind, value)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def normalize_handoff_mode(value: str) -> HandoffMode:
|
|
272
|
+
if value not in {"planning", "implementation", "validation", "review", "full"}:
|
|
273
|
+
raise LaunchError(f"Unsupported handoff mode: {value!r}")
|
|
274
|
+
return cast(HandoffMode, value)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def normalize_context_for(value: str) -> ContextFor:
|
|
278
|
+
normalized = {
|
|
279
|
+
"planning": "planner",
|
|
280
|
+
"implementation": "implementer",
|
|
281
|
+
"validation": "validator",
|
|
282
|
+
"review": "reviewer",
|
|
283
|
+
"spec": "spec-reviewer",
|
|
284
|
+
"code": "code-reviewer",
|
|
285
|
+
}.get(value, value)
|
|
286
|
+
if normalized not in {
|
|
287
|
+
"planner",
|
|
288
|
+
"implementer",
|
|
289
|
+
"validator",
|
|
290
|
+
"reviewer",
|
|
291
|
+
"spec-reviewer",
|
|
292
|
+
"code-reviewer",
|
|
293
|
+
"full",
|
|
294
|
+
}:
|
|
295
|
+
raise LaunchError(f"Unsupported context role: {value!r}")
|
|
296
|
+
return cast(ContextFor, normalized)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def normalize_context_scope(value: str) -> ContextScope:
|
|
300
|
+
if value not in {"task", "todo", "run"}:
|
|
301
|
+
raise LaunchError(f"Unsupported context scope: {value!r}")
|
|
302
|
+
return cast(ContextScope, value)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def normalize_context_format(value: str) -> ContextFormat:
|
|
306
|
+
if value not in {"markdown", "json", "text"}:
|
|
307
|
+
raise LaunchError(f"Unsupported context format: {value!r}")
|
|
308
|
+
return cast(ContextFormat, value)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def normalize_handoff_status(value: str) -> HandoffStatus:
|
|
312
|
+
if value not in {"open", "claimed", "closed", "cancelled"}:
|
|
313
|
+
raise LaunchError(f"Unsupported handoff status: {value!r}")
|
|
314
|
+
return cast(HandoffStatus, value)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def normalize_lock_policy(value: str) -> LockPolicy:
|
|
318
|
+
if value not in {"none", "retain", "release", "transfer"}:
|
|
319
|
+
raise LaunchError(f"Unsupported lock policy: {value!r}")
|
|
320
|
+
return cast(LockPolicy, value)
|
taskledger/errors.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TaskledgerError(Exception):
|
|
7
|
+
"""Base class for public taskledger errors."""
|
|
8
|
+
|
|
9
|
+
code = "TASKLEDGER_ERROR"
|
|
10
|
+
exit_code = 1
|
|
11
|
+
error_type = "TaskledgerError"
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
message: str,
|
|
16
|
+
*,
|
|
17
|
+
code: str | None = None,
|
|
18
|
+
exit_code: int | None = None,
|
|
19
|
+
error_type: str | None = None,
|
|
20
|
+
remediation: Sequence[str] | None = None,
|
|
21
|
+
details: Mapping[str, object] | None = None,
|
|
22
|
+
data: Mapping[str, object] | None = None,
|
|
23
|
+
task_id: str | None = None,
|
|
24
|
+
blocking_refs: Sequence[str] | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
resolved_code = code if code is not None else self.code
|
|
28
|
+
resolved_exit_code = exit_code if exit_code is not None else self.exit_code
|
|
29
|
+
resolved_error_type = error_type if error_type is not None else self.error_type
|
|
30
|
+
resolved_details = dict(details or data or {})
|
|
31
|
+
resolved_blocking_refs = tuple(str(item) for item in (blocking_refs or ()))
|
|
32
|
+
|
|
33
|
+
self.code = resolved_code
|
|
34
|
+
self.message = message
|
|
35
|
+
self.exit_code = resolved_exit_code
|
|
36
|
+
self.error_type = resolved_error_type
|
|
37
|
+
self.details = resolved_details
|
|
38
|
+
self.task_id = task_id
|
|
39
|
+
self.blocking_refs = resolved_blocking_refs
|
|
40
|
+
self.remediation = [str(item) for item in remediation or ()]
|
|
41
|
+
|
|
42
|
+
self.taskledger_exit_code = resolved_exit_code
|
|
43
|
+
self.taskledger_error_code = resolved_code
|
|
44
|
+
if error_type is not None:
|
|
45
|
+
self.taskledger_error_type = error_type
|
|
46
|
+
else:
|
|
47
|
+
self.taskledger_error_type = resolved_error_type
|
|
48
|
+
self.taskledger_remediation = list(self.remediation)
|
|
49
|
+
self.taskledger_data = self.to_error_payload()
|
|
50
|
+
|
|
51
|
+
def to_error_payload(self) -> dict[str, object]:
|
|
52
|
+
payload: dict[str, object] = {
|
|
53
|
+
"code": self.code,
|
|
54
|
+
"message": self.message,
|
|
55
|
+
}
|
|
56
|
+
if self.details:
|
|
57
|
+
payload["details"] = dict(self.details)
|
|
58
|
+
if self.task_id is not None:
|
|
59
|
+
payload["task_id"] = self.task_id
|
|
60
|
+
if self.blocking_refs:
|
|
61
|
+
payload["blocking_refs"] = list(self.blocking_refs)
|
|
62
|
+
return payload
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class UnsupportedAgentError(TaskledgerError):
|
|
66
|
+
"""Raised when the requested agent is not supported."""
|
|
67
|
+
|
|
68
|
+
code = "UNSUPPORTED_AGENT"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class AgentNotInstalledError(TaskledgerError):
|
|
72
|
+
"""Raised when the requested agent executable is not available."""
|
|
73
|
+
|
|
74
|
+
code = "AGENT_NOT_INSTALLED"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class LaunchError(TaskledgerError):
|
|
78
|
+
"""Raised when the child process cannot be prepared or started."""
|
|
79
|
+
|
|
80
|
+
code = "LAUNCH_ERROR"
|
|
81
|
+
error_type = "LaunchError"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class NoActiveTask(LaunchError):
|
|
85
|
+
code = "NO_ACTIVE_TASK"
|
|
86
|
+
exit_code = 5
|
|
87
|
+
error_type = "NoActiveTask"
|
|
88
|
+
|
|
89
|
+
def __init__(self) -> None:
|
|
90
|
+
super().__init__(
|
|
91
|
+
"No active task is set. Run `taskledger task activate <task-ref>` "
|
|
92
|
+
"or pass `--task <task-ref>`.",
|
|
93
|
+
remediation=[
|
|
94
|
+
"Run `taskledger task list` to find a task.",
|
|
95
|
+
"Run `taskledger task activate <task-ref>` to set the active task.",
|
|
96
|
+
"Or pass `--task <task-ref>` to this command.",
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class InvalidPromptError(TaskledgerError):
|
|
102
|
+
"""Raised when prompt input is empty or invalid."""
|
|
103
|
+
|
|
104
|
+
code = "INVALID_INPUT"
|
|
105
|
+
exit_code = 2
|
|
106
|
+
error_type = "ValidationError"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class NotFound(TaskledgerError):
|
|
110
|
+
code = "NOT_FOUND"
|
|
111
|
+
exit_code = 5
|
|
112
|
+
error_type = "NotFound"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ValidationError(TaskledgerError):
|
|
116
|
+
code = "VALIDATION_FAILED"
|
|
117
|
+
exit_code = 7
|
|
118
|
+
error_type = "ValidationError"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class InvalidStageTransition(TaskledgerError):
|
|
122
|
+
code = "INVALID_STAGE_TRANSITION"
|
|
123
|
+
exit_code = 3
|
|
124
|
+
error_type = "InvalidStageTransition"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ApprovalRequired(TaskledgerError):
|
|
128
|
+
code = "APPROVAL_REQUIRED"
|
|
129
|
+
exit_code = 3
|
|
130
|
+
error_type = "ApprovalRequired"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class DependencyIncomplete(TaskledgerError):
|
|
134
|
+
code = "DEPENDENCY_INCOMPLETE"
|
|
135
|
+
exit_code = 3
|
|
136
|
+
error_type = "DependencyIncomplete"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class LockConflict(LaunchError):
|
|
140
|
+
code = "LOCK_CONFLICT"
|
|
141
|
+
exit_code = 4
|
|
142
|
+
error_type = "LockConflict"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class StaleLockRequiresBreak(TaskledgerError):
|
|
146
|
+
code = "STALE_LOCK_REQUIRES_BREAK"
|
|
147
|
+
exit_code = 4
|
|
148
|
+
error_type = "StaleLockRequiresBreak"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class StorageCorruption(LaunchError):
|
|
152
|
+
code = "STORAGE_CORRUPTION"
|
|
153
|
+
exit_code = 6
|
|
154
|
+
error_type = "StorageCorruption"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ActiveTaskNotFound(StorageCorruption):
|
|
158
|
+
code = "ACTIVE_TASK_NOT_FOUND"
|
|
159
|
+
error_type = "StorageCorruption"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class IndexRebuildFailed(TaskledgerError):
|
|
163
|
+
code = "INDEX_REBUILD_FAILED"
|
|
164
|
+
exit_code = 6
|
|
165
|
+
error_type = "IndexRebuildFailed"
|