donna 0.2.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.
- donna/__init__.py +1 -0
- donna/artifacts/__init__.py +0 -0
- donna/artifacts/usage/__init__.py +0 -0
- donna/artifacts/usage/artifacts.md +224 -0
- donna/artifacts/usage/cli.md +117 -0
- donna/artifacts/usage/worlds.md +36 -0
- donna/artifacts/work/__init__.py +0 -0
- donna/artifacts/work/do_it.md +142 -0
- donna/artifacts/work/do_it_fast.md +98 -0
- donna/artifacts/work/planning.md +245 -0
- donna/cli/__init__.py +0 -0
- donna/cli/__main__.py +6 -0
- donna/cli/application.py +17 -0
- donna/cli/commands/__init__.py +0 -0
- donna/cli/commands/artifacts.py +110 -0
- donna/cli/commands/projects.py +49 -0
- donna/cli/commands/sessions.py +77 -0
- donna/cli/types.py +138 -0
- donna/cli/utils.py +53 -0
- donna/core/__init__.py +0 -0
- donna/core/entities.py +27 -0
- donna/core/errors.py +126 -0
- donna/core/result.py +99 -0
- donna/core/utils.py +37 -0
- donna/domain/__init__.py +0 -0
- donna/domain/errors.py +47 -0
- donna/domain/ids.py +497 -0
- donna/lib/__init__.py +21 -0
- donna/lib/sources.py +5 -0
- donna/lib/worlds.py +7 -0
- donna/machine/__init__.py +0 -0
- donna/machine/action_requests.py +50 -0
- donna/machine/artifacts.py +200 -0
- donna/machine/changes.py +91 -0
- donna/machine/errors.py +122 -0
- donna/machine/operations.py +31 -0
- donna/machine/primitives.py +77 -0
- donna/machine/sessions.py +215 -0
- donna/machine/state.py +244 -0
- donna/machine/tasks.py +89 -0
- donna/machine/templates.py +83 -0
- donna/primitives/__init__.py +1 -0
- donna/primitives/artifacts/__init__.py +0 -0
- donna/primitives/artifacts/specification.py +20 -0
- donna/primitives/artifacts/workflow.py +195 -0
- donna/primitives/directives/__init__.py +0 -0
- donna/primitives/directives/goto.py +44 -0
- donna/primitives/directives/task_variable.py +73 -0
- donna/primitives/directives/view.py +45 -0
- donna/primitives/operations/__init__.py +0 -0
- donna/primitives/operations/finish_workflow.py +37 -0
- donna/primitives/operations/request_action.py +89 -0
- donna/primitives/operations/run_script.py +250 -0
- donna/protocol/__init__.py +0 -0
- donna/protocol/cell_shortcuts.py +9 -0
- donna/protocol/cells.py +44 -0
- donna/protocol/errors.py +17 -0
- donna/protocol/formatters/__init__.py +0 -0
- donna/protocol/formatters/automation.py +25 -0
- donna/protocol/formatters/base.py +15 -0
- donna/protocol/formatters/human.py +36 -0
- donna/protocol/formatters/llm.py +39 -0
- donna/protocol/modes.py +40 -0
- donna/protocol/nodes.py +59 -0
- donna/world/__init__.py +0 -0
- donna/world/artifacts.py +122 -0
- donna/world/artifacts_discovery.py +90 -0
- donna/world/config.py +198 -0
- donna/world/errors.py +232 -0
- donna/world/initialization.py +42 -0
- donna/world/markdown.py +267 -0
- donna/world/sources/__init__.py +1 -0
- donna/world/sources/base.py +62 -0
- donna/world/sources/markdown.py +260 -0
- donna/world/templates.py +181 -0
- donna/world/tmp.py +33 -0
- donna/world/worlds/__init__.py +0 -0
- donna/world/worlds/base.py +68 -0
- donna/world/worlds/filesystem.py +189 -0
- donna/world/worlds/python.py +196 -0
- donna-0.2.0.dist-info/METADATA +44 -0
- donna-0.2.0.dist-info/RECORD +85 -0
- donna-0.2.0.dist-info/WHEEL +4 -0
- donna-0.2.0.dist-info/entry_points.txt +3 -0
- donna-0.2.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from typing import Callable, ParamSpec
|
|
3
|
+
|
|
4
|
+
from donna.core.errors import ErrorsList
|
|
5
|
+
from donna.core.result import Err, Ok, Result, unwrap_to_error
|
|
6
|
+
from donna.domain.ids import ActionRequestId, FullArtifactId, FullArtifactSectionId, WorldId
|
|
7
|
+
from donna.machine import errors as machine_errors
|
|
8
|
+
from donna.machine.operations import OperationMeta
|
|
9
|
+
from donna.machine.state import ConsistentState, MutableState
|
|
10
|
+
from donna.protocol.cell_shortcuts import operation_succeeded
|
|
11
|
+
from donna.protocol.cells import Cell
|
|
12
|
+
from donna.world import artifacts
|
|
13
|
+
from donna.world import tmp as world_tmp
|
|
14
|
+
from donna.world.config import config
|
|
15
|
+
from donna.world.worlds.base import World
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _errors_to_cells(errors: ErrorsList) -> list[Cell]:
|
|
19
|
+
return [error.node().info() for error in errors]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@unwrap_to_error
|
|
23
|
+
def _session() -> Result[World, ErrorsList]:
|
|
24
|
+
world = config().get_world(WorldId("session")).unwrap()
|
|
25
|
+
|
|
26
|
+
if not world.is_initialized():
|
|
27
|
+
world.initialize(reset=False)
|
|
28
|
+
|
|
29
|
+
return Ok(world)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@unwrap_to_error
|
|
33
|
+
def _load_state() -> Result[ConsistentState, ErrorsList]:
|
|
34
|
+
content = _session().unwrap().read_state("state.json").unwrap()
|
|
35
|
+
if content is None:
|
|
36
|
+
return Err([machine_errors.SessionStateNotInitialized()])
|
|
37
|
+
|
|
38
|
+
return Ok(ConsistentState.from_json(content.decode("utf-8")))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@unwrap_to_error
|
|
42
|
+
def _save_state(state: ConsistentState) -> Result[None, ErrorsList]:
|
|
43
|
+
_session().unwrap().write_state("state.json", state.to_json().encode("utf-8")).unwrap()
|
|
44
|
+
return Ok(None)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@unwrap_to_error
|
|
48
|
+
def _state_run(mutator: MutableState) -> Result[None, ErrorsList]:
|
|
49
|
+
while mutator.has_work():
|
|
50
|
+
mutator.exectute_next_work_unit().unwrap()
|
|
51
|
+
_save_state(mutator.freeze()).unwrap()
|
|
52
|
+
|
|
53
|
+
return Ok(None)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@unwrap_to_error
|
|
57
|
+
def _state_cells() -> Result[list[Cell], ErrorsList]:
|
|
58
|
+
return Ok(_load_state().unwrap().node().details())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
P = ParamSpec("P")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _session_required(func: Callable[P, list[Cell]]) -> Callable[P, list[Cell]]:
|
|
65
|
+
# TODO: refactor to catch domain exception from load_state
|
|
66
|
+
# when we implement domain exceptions
|
|
67
|
+
@functools.wraps(func)
|
|
68
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> list[Cell]:
|
|
69
|
+
state_result = _load_state()
|
|
70
|
+
if state_result.is_err():
|
|
71
|
+
return _errors_to_cells(state_result.unwrap_err())
|
|
72
|
+
|
|
73
|
+
return func(*args, **kwargs)
|
|
74
|
+
|
|
75
|
+
return wrapper
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def start() -> list[Cell]:
|
|
79
|
+
world_tmp.clear()
|
|
80
|
+
session_result = _session()
|
|
81
|
+
if session_result.is_err():
|
|
82
|
+
return _errors_to_cells(session_result.unwrap_err())
|
|
83
|
+
|
|
84
|
+
session_result.unwrap().initialize(reset=True)
|
|
85
|
+
|
|
86
|
+
save_result = _save_state(MutableState.build().freeze())
|
|
87
|
+
if save_result.is_err():
|
|
88
|
+
return _errors_to_cells(save_result.unwrap_err())
|
|
89
|
+
|
|
90
|
+
return [operation_succeeded("Started new session.")]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def clear() -> list[Cell]:
|
|
94
|
+
world_tmp.clear()
|
|
95
|
+
session_result = _session()
|
|
96
|
+
if session_result.is_err():
|
|
97
|
+
return _errors_to_cells(session_result.unwrap_err())
|
|
98
|
+
|
|
99
|
+
session_result.unwrap().initialize(reset=True)
|
|
100
|
+
return [operation_succeeded("Cleared session.")]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@_session_required
|
|
104
|
+
def continue_() -> list[Cell]:
|
|
105
|
+
state_result = _load_state()
|
|
106
|
+
if state_result.is_err():
|
|
107
|
+
return _errors_to_cells(state_result.unwrap_err())
|
|
108
|
+
|
|
109
|
+
mutator = state_result.unwrap().mutator()
|
|
110
|
+
run_result = _state_run(mutator)
|
|
111
|
+
if run_result.is_err():
|
|
112
|
+
return _errors_to_cells(run_result.unwrap_err())
|
|
113
|
+
|
|
114
|
+
cells_result = _state_cells()
|
|
115
|
+
if cells_result.is_err():
|
|
116
|
+
return _errors_to_cells(cells_result.unwrap_err())
|
|
117
|
+
|
|
118
|
+
return cells_result.unwrap()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@_session_required
|
|
122
|
+
def status() -> list[Cell]:
|
|
123
|
+
state_result = _load_state()
|
|
124
|
+
if state_result.is_err():
|
|
125
|
+
return _errors_to_cells(state_result.unwrap_err())
|
|
126
|
+
|
|
127
|
+
return [state_result.unwrap().node().info()]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@_session_required
|
|
131
|
+
def details() -> list[Cell]:
|
|
132
|
+
state_result = _load_state()
|
|
133
|
+
if state_result.is_err():
|
|
134
|
+
return _errors_to_cells(state_result.unwrap_err())
|
|
135
|
+
|
|
136
|
+
return state_result.unwrap().node().details()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@_session_required
|
|
140
|
+
def start_workflow(artifact_id: FullArtifactId) -> list[Cell]:
|
|
141
|
+
workflow_result = artifacts.load_artifact(artifact_id)
|
|
142
|
+
if workflow_result.is_err():
|
|
143
|
+
return _errors_to_cells(workflow_result.unwrap_err())
|
|
144
|
+
|
|
145
|
+
workflow = workflow_result.unwrap()
|
|
146
|
+
primary_section_result = workflow.primary_section()
|
|
147
|
+
if primary_section_result.is_err():
|
|
148
|
+
return _errors_to_cells(primary_section_result.unwrap_err())
|
|
149
|
+
|
|
150
|
+
primary_section = primary_section_result.unwrap()
|
|
151
|
+
|
|
152
|
+
state_result = _load_state()
|
|
153
|
+
if state_result.is_err():
|
|
154
|
+
return _errors_to_cells(state_result.unwrap_err())
|
|
155
|
+
|
|
156
|
+
mutator = state_result.unwrap().mutator()
|
|
157
|
+
mutator.start_workflow(workflow.id.to_full_local(primary_section.id))
|
|
158
|
+
save_result = _save_state(mutator.freeze())
|
|
159
|
+
if save_result.is_err():
|
|
160
|
+
return _errors_to_cells(save_result.unwrap_err())
|
|
161
|
+
|
|
162
|
+
run_result = _state_run(mutator)
|
|
163
|
+
if run_result.is_err():
|
|
164
|
+
return _errors_to_cells(run_result.unwrap_err())
|
|
165
|
+
|
|
166
|
+
cells_result = _state_cells()
|
|
167
|
+
if cells_result.is_err():
|
|
168
|
+
return _errors_to_cells(cells_result.unwrap_err())
|
|
169
|
+
|
|
170
|
+
return cells_result.unwrap()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@unwrap_to_error
|
|
174
|
+
def _validate_operation_transition(
|
|
175
|
+
state: MutableState, request_id: ActionRequestId, next_operation_id: FullArtifactSectionId
|
|
176
|
+
) -> Result[None, ErrorsList]:
|
|
177
|
+
operation_id = state.get_action_request(request_id).unwrap().operation_id
|
|
178
|
+
workflow = artifacts.load_artifact(operation_id.full_artifact_id).unwrap()
|
|
179
|
+
operation = workflow.get_section(operation_id.local_id).unwrap()
|
|
180
|
+
|
|
181
|
+
assert isinstance(operation.meta, OperationMeta)
|
|
182
|
+
|
|
183
|
+
if next_operation_id.local_id not in operation.meta.allowed_transtions:
|
|
184
|
+
return Err(
|
|
185
|
+
[machine_errors.InvalidOperationTransition(operation_id=operation_id, next_operation_id=next_operation_id)]
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return Ok(None)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@_session_required
|
|
192
|
+
def complete_action_request(request_id: ActionRequestId, next_operation_id: FullArtifactSectionId) -> list[Cell]:
|
|
193
|
+
state_result = _load_state()
|
|
194
|
+
if state_result.is_err():
|
|
195
|
+
return _errors_to_cells(state_result.unwrap_err())
|
|
196
|
+
|
|
197
|
+
mutator = state_result.unwrap().mutator()
|
|
198
|
+
transition_result = _validate_operation_transition(mutator, request_id, next_operation_id)
|
|
199
|
+
if transition_result.is_err():
|
|
200
|
+
return _errors_to_cells(transition_result.unwrap_err())
|
|
201
|
+
|
|
202
|
+
mutator.complete_action_request(request_id, next_operation_id)
|
|
203
|
+
save_result = _save_state(mutator.freeze())
|
|
204
|
+
if save_result.is_err():
|
|
205
|
+
return _errors_to_cells(save_result.unwrap_err())
|
|
206
|
+
|
|
207
|
+
run_result = _state_run(mutator)
|
|
208
|
+
if run_result.is_err():
|
|
209
|
+
return _errors_to_cells(run_result.unwrap_err())
|
|
210
|
+
|
|
211
|
+
cells_result = _state_cells()
|
|
212
|
+
if cells_result.is_err():
|
|
213
|
+
return _errors_to_cells(cells_result.unwrap_err())
|
|
214
|
+
|
|
215
|
+
return cells_result.unwrap()
|
donna/machine/state.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import textwrap
|
|
3
|
+
from typing import Sequence, cast
|
|
4
|
+
|
|
5
|
+
import pydantic
|
|
6
|
+
|
|
7
|
+
from donna.core.entities import BaseEntity
|
|
8
|
+
from donna.core.errors import ErrorsList
|
|
9
|
+
from donna.core.result import Err, Ok, Result, unwrap_to_error
|
|
10
|
+
from donna.domain.ids import (
|
|
11
|
+
ActionRequestId,
|
|
12
|
+
FullArtifactSectionId,
|
|
13
|
+
InternalId,
|
|
14
|
+
TaskId,
|
|
15
|
+
WorkUnitId,
|
|
16
|
+
)
|
|
17
|
+
from donna.machine import errors as machine_errors
|
|
18
|
+
from donna.machine.action_requests import ActionRequest
|
|
19
|
+
from donna.machine.changes import (
|
|
20
|
+
Change,
|
|
21
|
+
ChangeAddTask,
|
|
22
|
+
ChangeAddWorkUnit,
|
|
23
|
+
ChangeRemoveActionRequest,
|
|
24
|
+
ChangeRemoveTask,
|
|
25
|
+
ChangeRemoveWorkUnit,
|
|
26
|
+
)
|
|
27
|
+
from donna.machine.tasks import Task, WorkUnit
|
|
28
|
+
from donna.protocol.cells import Cell
|
|
29
|
+
from donna.protocol.nodes import Node
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BaseState(BaseEntity):
|
|
33
|
+
tasks: list[Task]
|
|
34
|
+
work_units: list[WorkUnit]
|
|
35
|
+
action_requests: list[ActionRequest]
|
|
36
|
+
started: bool
|
|
37
|
+
last_id: int
|
|
38
|
+
|
|
39
|
+
def has_work(self) -> bool:
|
|
40
|
+
return bool(self.work_units)
|
|
41
|
+
|
|
42
|
+
def node(self) -> "StateNode":
|
|
43
|
+
return StateNode(self)
|
|
44
|
+
|
|
45
|
+
###########
|
|
46
|
+
# Accessors
|
|
47
|
+
###########
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def current_task(self) -> Task:
|
|
51
|
+
return self.tasks[-1]
|
|
52
|
+
|
|
53
|
+
def get_action_request(self, request_id: ActionRequestId) -> Result[ActionRequest, ErrorsList]:
|
|
54
|
+
for request in self.action_requests:
|
|
55
|
+
if request.id == request_id:
|
|
56
|
+
return Ok(request)
|
|
57
|
+
|
|
58
|
+
return Err([machine_errors.ActionRequestNotFound(request_id=request_id)])
|
|
59
|
+
|
|
60
|
+
# Currently we execute first work unit found for the current task
|
|
61
|
+
# Since we only append work units, this effectively works as a queue per task
|
|
62
|
+
# In the future we may want to have more sophisticated scheduling
|
|
63
|
+
def get_next_work_unit(self) -> WorkUnit | None:
|
|
64
|
+
for work_unit in self.work_units:
|
|
65
|
+
if work_unit.task_id != self.current_task.id:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
return work_unit
|
|
69
|
+
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ConsistentState(BaseState):
|
|
74
|
+
|
|
75
|
+
def mutator(self) -> "MutableState":
|
|
76
|
+
return MutableState.model_validate(copy.deepcopy(self.model_dump()))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class MutableState(BaseState):
|
|
80
|
+
model_config = pydantic.ConfigDict(frozen=False)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def build(cls) -> "MutableState":
|
|
84
|
+
return cls(
|
|
85
|
+
tasks=[],
|
|
86
|
+
action_requests=[],
|
|
87
|
+
work_units=[],
|
|
88
|
+
started=False,
|
|
89
|
+
last_id=0,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def freeze(self) -> ConsistentState:
|
|
93
|
+
return ConsistentState.model_validate(copy.deepcopy(self.model_dump()))
|
|
94
|
+
|
|
95
|
+
################
|
|
96
|
+
# Ids generation
|
|
97
|
+
################
|
|
98
|
+
|
|
99
|
+
def next_id(self, prefix: str) -> InternalId:
|
|
100
|
+
self.last_id += 1
|
|
101
|
+
new_id = InternalId.build(prefix, self.last_id)
|
|
102
|
+
return new_id
|
|
103
|
+
|
|
104
|
+
def next_task_id(self) -> TaskId:
|
|
105
|
+
return cast(TaskId, self.next_id("T"))
|
|
106
|
+
|
|
107
|
+
def next_work_unit_id(self) -> WorkUnitId:
|
|
108
|
+
return cast(WorkUnitId, self.next_id("WU"))
|
|
109
|
+
|
|
110
|
+
def next_action_request_id(self) -> ActionRequestId:
|
|
111
|
+
return cast(ActionRequestId, self.next_id("AR"))
|
|
112
|
+
|
|
113
|
+
##########
|
|
114
|
+
# Mutators
|
|
115
|
+
##########
|
|
116
|
+
|
|
117
|
+
def mark_started(self) -> None:
|
|
118
|
+
self.started = True
|
|
119
|
+
|
|
120
|
+
def add_action_request(self, action_request: ActionRequest) -> None:
|
|
121
|
+
action_request.id = self.next_action_request_id()
|
|
122
|
+
self.action_requests.append(action_request)
|
|
123
|
+
|
|
124
|
+
def add_work_unit(self, work_unit: WorkUnit) -> None:
|
|
125
|
+
self.work_units.append(work_unit)
|
|
126
|
+
|
|
127
|
+
def add_task(self, task: Task) -> None:
|
|
128
|
+
self.tasks.append(task)
|
|
129
|
+
|
|
130
|
+
def remove_action_request(self, request_id: ActionRequestId) -> None:
|
|
131
|
+
self.action_requests = [request for request in self.action_requests if request.id != request_id]
|
|
132
|
+
|
|
133
|
+
def remove_work_unit(self, work_unit_id: WorkUnitId) -> None:
|
|
134
|
+
self.work_units = [unit for unit in self.work_units if unit.id != work_unit_id]
|
|
135
|
+
|
|
136
|
+
def remove_task(self, task_id: TaskId) -> None:
|
|
137
|
+
self.tasks = [task for task in self.tasks if task.id != task_id]
|
|
138
|
+
|
|
139
|
+
def apply_changes(self, changes: Sequence[Change]) -> None:
|
|
140
|
+
for change in changes:
|
|
141
|
+
change.apply_to(self)
|
|
142
|
+
|
|
143
|
+
####################
|
|
144
|
+
# Complex operations
|
|
145
|
+
####################
|
|
146
|
+
|
|
147
|
+
def complete_action_request(self, request_id: ActionRequestId, next_operation_id: FullArtifactSectionId) -> None:
|
|
148
|
+
changes = [
|
|
149
|
+
ChangeAddWorkUnit(task_id=self.current_task.id, operation_id=next_operation_id),
|
|
150
|
+
ChangeRemoveActionRequest(action_request_id=request_id),
|
|
151
|
+
]
|
|
152
|
+
self.apply_changes(changes)
|
|
153
|
+
|
|
154
|
+
def start_workflow(self, full_operation_id: FullArtifactSectionId) -> None:
|
|
155
|
+
changes = [ChangeAddTask(operation_id=full_operation_id)]
|
|
156
|
+
self.apply_changes(changes)
|
|
157
|
+
|
|
158
|
+
def finish_workflow(self, task_id: TaskId) -> None:
|
|
159
|
+
changes = [ChangeRemoveTask(task_id=task_id)]
|
|
160
|
+
self.apply_changes(changes)
|
|
161
|
+
|
|
162
|
+
@unwrap_to_error
|
|
163
|
+
def exectute_next_work_unit(self) -> Result[None, ErrorsList]:
|
|
164
|
+
next_work_unit = self.get_next_work_unit()
|
|
165
|
+
assert next_work_unit is not None
|
|
166
|
+
|
|
167
|
+
changes = next_work_unit.run(self.current_task).unwrap()
|
|
168
|
+
changes.append(ChangeRemoveWorkUnit(work_unit_id=next_work_unit.id))
|
|
169
|
+
|
|
170
|
+
self.apply_changes(changes)
|
|
171
|
+
return Ok(None)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class StateNode(Node):
|
|
175
|
+
__slots__ = ("_state",)
|
|
176
|
+
|
|
177
|
+
def __init__(self, state: BaseState) -> None:
|
|
178
|
+
self._state = state
|
|
179
|
+
|
|
180
|
+
def status(self) -> Cell:
|
|
181
|
+
if not self._state.started:
|
|
182
|
+
message = textwrap.dedent(
|
|
183
|
+
"""
|
|
184
|
+
The session has not been started yet. You can safely start a new session and then run a workflow.
|
|
185
|
+
"""
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
elif not self._state.tasks:
|
|
189
|
+
message = textwrap.dedent(
|
|
190
|
+
"""
|
|
191
|
+
The session is IDLE. There are no active tasks.
|
|
192
|
+
|
|
193
|
+
- If the developer asked you to start working on a new task, you can do so by initiating a new workflow.
|
|
194
|
+
- If you have been working on a task, consider it completed and REPORT THE RESULTS TO THE DEVELOPER.
|
|
195
|
+
"""
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
elif self._state.work_units:
|
|
199
|
+
message = textwrap.dedent(
|
|
200
|
+
"""
|
|
201
|
+
The session has PENDING WORK UNITS. Donna has work to complete.
|
|
202
|
+
|
|
203
|
+
- If the developer asked you to start working on a new task, you MUST warn that there are pending work
|
|
204
|
+
units and ask if you should start a new session or continue working on the current work units.
|
|
205
|
+
- If you have been working on a task, you can continue session.
|
|
206
|
+
"""
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
elif self._state.action_requests:
|
|
210
|
+
message = textwrap.dedent(
|
|
211
|
+
"""
|
|
212
|
+
The session is AWAITING YOUR ACTION. You have pending action requests to address.
|
|
213
|
+
|
|
214
|
+
- If the developer asked you to start working on a new task, you MUST ask if you should start a new session
|
|
215
|
+
or continue working on the current action requests.
|
|
216
|
+
- Otherwise, you MUST address the pending action requests before proceeding.
|
|
217
|
+
"""
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
elif self._state.tasks:
|
|
221
|
+
message = textwrap.dedent(
|
|
222
|
+
"""
|
|
223
|
+
The session has unfinished TASKS but no pending work units or action requests.
|
|
224
|
+
|
|
225
|
+
- If the developer asked you to start working on a new task , you MUST ask if you should start a new
|
|
226
|
+
session or run a new workflow in the current one.
|
|
227
|
+
- If you have been working on a task, you can consider it completed and output the results to the
|
|
228
|
+
developer.
|
|
229
|
+
"""
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
else:
|
|
233
|
+
raise machine_errors.SessionStateStatusInvalid()
|
|
234
|
+
|
|
235
|
+
return Cell.build_markdown(
|
|
236
|
+
kind="session_state_status",
|
|
237
|
+
content=message,
|
|
238
|
+
tasks=len(self._state.tasks),
|
|
239
|
+
queued_work_units=len(self._state.work_units),
|
|
240
|
+
pending_action_requests=len(self._state.action_requests),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def references(self) -> list[Node]:
|
|
244
|
+
return [action_request.node() for action_request in self._state.action_requests]
|
donna/machine/tasks.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import sys
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
import pydantic
|
|
6
|
+
|
|
7
|
+
from donna.core.entities import BaseEntity
|
|
8
|
+
from donna.core.errors import ErrorsList
|
|
9
|
+
from donna.core.result import Ok, Result, unwrap_to_error
|
|
10
|
+
from donna.domain.ids import FullArtifactSectionId, TaskId, WorkUnitId
|
|
11
|
+
from donna.protocol.cells import Cell
|
|
12
|
+
from donna.protocol.modes import get_cell_formatter
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from donna.machine.changes import Change
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Task(BaseEntity):
|
|
19
|
+
id: TaskId
|
|
20
|
+
context: dict[str, Any]
|
|
21
|
+
|
|
22
|
+
# TODO: we may want to make queue items frozen later
|
|
23
|
+
model_config = pydantic.ConfigDict(frozen=False)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def build(cls, id: TaskId) -> "Task":
|
|
27
|
+
return Task(
|
|
28
|
+
id=id,
|
|
29
|
+
context={},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WorkUnit(BaseEntity):
|
|
34
|
+
id: WorkUnitId
|
|
35
|
+
task_id: TaskId
|
|
36
|
+
operation_id: FullArtifactSectionId
|
|
37
|
+
context: dict[str, Any]
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def build(
|
|
41
|
+
cls,
|
|
42
|
+
id: WorkUnitId,
|
|
43
|
+
task_id: TaskId,
|
|
44
|
+
operation_id: FullArtifactSectionId,
|
|
45
|
+
context: dict[str, Any] | None = None,
|
|
46
|
+
) -> "WorkUnit":
|
|
47
|
+
|
|
48
|
+
if context is None:
|
|
49
|
+
context = {}
|
|
50
|
+
|
|
51
|
+
unit = cls(
|
|
52
|
+
task_id=task_id,
|
|
53
|
+
id=id,
|
|
54
|
+
operation_id=operation_id,
|
|
55
|
+
context=copy.deepcopy(context),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return unit
|
|
59
|
+
|
|
60
|
+
@unwrap_to_error
|
|
61
|
+
def run(self, task: Task) -> Result[list["Change"], ErrorsList]:
|
|
62
|
+
from donna.machine import artifacts as machine_artifacts
|
|
63
|
+
from donna.machine.primitives import resolve_primitive
|
|
64
|
+
from donna.world.artifacts import ArtifactRenderContext
|
|
65
|
+
from donna.world.templates import RenderMode
|
|
66
|
+
|
|
67
|
+
render_context = ArtifactRenderContext(
|
|
68
|
+
primary_mode=RenderMode.execute,
|
|
69
|
+
current_task=task,
|
|
70
|
+
current_work_unit=self,
|
|
71
|
+
)
|
|
72
|
+
operation = machine_artifacts.resolve(self.operation_id, render_context).unwrap()
|
|
73
|
+
operation_kind = resolve_primitive(operation.kind).unwrap()
|
|
74
|
+
|
|
75
|
+
##########################
|
|
76
|
+
# We log each operation here to help agent display the progress to the user
|
|
77
|
+
# TODO: not a good solution from the agent perspective
|
|
78
|
+
# let's hope there will some protocol appear that helps with that later
|
|
79
|
+
# TODO: not so good place for and way of logging, should do smth with that
|
|
80
|
+
log_message = f"{self.operation_id}: {operation.title}"
|
|
81
|
+
log_cell = Cell.build(kind="donna_log", media_type="text/plain", content=log_message)
|
|
82
|
+
formatter = get_cell_formatter()
|
|
83
|
+
sys.stdout.buffer.write(formatter.format_log(log_cell, single_mode=True) + b"\n\n")
|
|
84
|
+
sys.stdout.buffer.flush()
|
|
85
|
+
##########################
|
|
86
|
+
|
|
87
|
+
changes = list(operation_kind.execute_section(task, self, operation))
|
|
88
|
+
|
|
89
|
+
return Ok(changes)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TYPE_CHECKING, Any, TypeAlias
|
|
3
|
+
|
|
4
|
+
from jinja2.runtime import Context
|
|
5
|
+
|
|
6
|
+
from donna.core.errors import ErrorsList
|
|
7
|
+
from donna.core.result import Ok, Result
|
|
8
|
+
from donna.machine import errors as machine_errors
|
|
9
|
+
from donna.machine.primitives import Primitive
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
PreparedDirectiveArguments: TypeAlias = tuple[Any, ...]
|
|
15
|
+
PreparedDirectiveResult: TypeAlias = Result[PreparedDirectiveArguments, ErrorsList]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DirectiveUnsupportedRenderMode(machine_errors.InternalError):
|
|
19
|
+
message: str = "Render mode {render_mode} not implemented in directive {directive_name}."
|
|
20
|
+
render_mode: Any
|
|
21
|
+
directive_name: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Directive(Primitive, ABC):
|
|
25
|
+
analyze_id: str
|
|
26
|
+
|
|
27
|
+
def apply_directive( # noqa: E704
|
|
28
|
+
self,
|
|
29
|
+
context: Context,
|
|
30
|
+
*argv: Any,
|
|
31
|
+
) -> Result[Any, ErrorsList]:
|
|
32
|
+
from donna.world import templates as world_templates
|
|
33
|
+
|
|
34
|
+
render_mode = context["render_mode"]
|
|
35
|
+
arguments_result = self._prepare_arguments(context, *argv)
|
|
36
|
+
if arguments_result.is_err():
|
|
37
|
+
return arguments_result
|
|
38
|
+
|
|
39
|
+
argv = arguments_result.unwrap()
|
|
40
|
+
|
|
41
|
+
match render_mode:
|
|
42
|
+
case world_templates.RenderMode.view:
|
|
43
|
+
return self.render_view(context, *argv)
|
|
44
|
+
case world_templates.RenderMode.execute:
|
|
45
|
+
return self.render_execute(context, *argv)
|
|
46
|
+
case world_templates.RenderMode.analysis:
|
|
47
|
+
return self.render_analyze(context, *argv)
|
|
48
|
+
case _:
|
|
49
|
+
raise DirectiveUnsupportedRenderMode(render_mode=render_mode, directive_name=self.__class__.__name__)
|
|
50
|
+
|
|
51
|
+
def _prepare_arguments(
|
|
52
|
+
self,
|
|
53
|
+
context: Context,
|
|
54
|
+
*argv: Any,
|
|
55
|
+
) -> PreparedDirectiveResult:
|
|
56
|
+
return Ok(argv)
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def render_view( # noqa: E704
|
|
60
|
+
self,
|
|
61
|
+
context: Context,
|
|
62
|
+
*argv: Any,
|
|
63
|
+
) -> Result[Any, ErrorsList]: ...
|
|
64
|
+
|
|
65
|
+
def render_execute(
|
|
66
|
+
self,
|
|
67
|
+
context: Context,
|
|
68
|
+
*argv: Any,
|
|
69
|
+
) -> Result[Any, ErrorsList]:
|
|
70
|
+
return self.render_view(context, *argv)
|
|
71
|
+
|
|
72
|
+
def render_analyze(
|
|
73
|
+
self,
|
|
74
|
+
context: Context,
|
|
75
|
+
*argv: Any,
|
|
76
|
+
) -> Result[str, ErrorsList]:
|
|
77
|
+
parts = [str(arg) for arg in argv]
|
|
78
|
+
arguments = " ".join(parts)
|
|
79
|
+
|
|
80
|
+
if arguments:
|
|
81
|
+
return Ok(f"$$donna {self.analyze_id} {arguments} donna$$")
|
|
82
|
+
|
|
83
|
+
return Ok(f"$$donna {self.analyze_id} donna$$")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Concrete implementations for Donna primitives."""
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
2
|
+
|
|
3
|
+
from donna.machine.artifacts import ArtifactSectionConfig
|
|
4
|
+
from donna.machine.primitives import Primitive
|
|
5
|
+
from donna.world.sources.markdown import MarkdownSectionMixin
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TextConfig(ArtifactSectionConfig):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Text(MarkdownSectionMixin, Primitive):
|
|
16
|
+
config_class: ClassVar[type[TextConfig]] = TextConfig
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Specification(MarkdownSectionMixin, Primitive):
|
|
20
|
+
pass
|