levain-cli 0.1.14__tar.gz → 0.1.15__tar.gz
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.
- {levain_cli-0.1.14 → levain_cli-0.1.15}/.gitignore +7 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/PKG-INFO +1 -1
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/models.py +4 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/validate.py +53 -1
- {levain_cli-0.1.14 → levain_cli-0.1.15}/pyproject.toml +1 -1
- {levain_cli-0.1.14 → levain_cli-0.1.15}/LICENSE +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/README.md +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/__init__.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/__main__.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/_format.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/_java.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/auth.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/cli.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/codegen.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/graph.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/integrations_check.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/known_recipes.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/mcp.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/parser.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/profile.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/py.typed +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/reducers.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/source_check.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/tlc.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/translate.py +0 -0
- {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/user_input.py +0 -0
|
@@ -18,6 +18,10 @@ build/
|
|
|
18
18
|
!**/platform_agents/build/
|
|
19
19
|
!**/src/build/
|
|
20
20
|
|
|
21
|
+
# Maven build output
|
|
22
|
+
services/flink/target/
|
|
23
|
+
services/flink/dependency-reduced-pom.xml
|
|
24
|
+
|
|
21
25
|
# Binaries
|
|
22
26
|
bin/
|
|
23
27
|
|
|
@@ -136,3 +140,6 @@ assets/video/*/renders/
|
|
|
136
140
|
# www/public/videos/, the composition that made them does not.
|
|
137
141
|
assets/video/updates-*/
|
|
138
142
|
assets/video/*/snapshots/
|
|
143
|
+
|
|
144
|
+
# Maven build output (Flink job); the image build compiles from source.
|
|
145
|
+
services/flink/target/
|
|
@@ -134,6 +134,10 @@ class ValidatedSpec:
|
|
|
134
134
|
routing: list[RoutingTable]
|
|
135
135
|
invariants: list[str] = field(default_factory=list)
|
|
136
136
|
constants: dict[str, Any] = field(default_factory=dict)
|
|
137
|
+
# Lanes the recipe splits its turns into (``\* @lanes [...]``):
|
|
138
|
+
# runs in different lanes may share the agent's workspace at the
|
|
139
|
+
# same time. Empty when the recipe runs one turn at a time.
|
|
140
|
+
lanes: list[str] = field(default_factory=list)
|
|
137
141
|
|
|
138
142
|
|
|
139
143
|
@dataclass(frozen=True)
|
|
@@ -6,11 +6,12 @@ validation (PlusCal + Python source + pyproject.toml).
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import json
|
|
9
10
|
import logging
|
|
10
11
|
import os
|
|
11
12
|
import re
|
|
12
13
|
import shutil
|
|
13
|
-
from typing import cast
|
|
14
|
+
from typing import Any, cast
|
|
14
15
|
|
|
15
16
|
from ._java import resolve_jar
|
|
16
17
|
from .graph import check_graph
|
|
@@ -52,6 +53,53 @@ _RE_COMMENT_PREFIX = re.compile(r"^\\[*]\s?")
|
|
|
52
53
|
# excluded from the free-form description so the help text never
|
|
53
54
|
# accidentally includes machine-readable metadata.
|
|
54
55
|
_RE_ANNOTATION = re.compile(r"^\\[*]\s*@\w+")
|
|
56
|
+
# Module-level ``\* @lanes ["people"]``: the lanes whose turns may run
|
|
57
|
+
# at once on the agent's persistent workspace. ``people`` is the
|
|
58
|
+
# only lane a recipe can split out today; a trigger delivery or a
|
|
59
|
+
# scheduled fire stays in the default lane.
|
|
60
|
+
_RE_LANES = re.compile(r"^\s*\\[*]\s*@lanes\s+(\[.*\])\s*$")
|
|
61
|
+
KNOWN_LANES = frozenset({"people"})
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def extract_lanes(source: str) -> list[str] | ValidationError:
|
|
65
|
+
r"""The lanes a ``\* @lanes`` line declares; ``[]`` without one."""
|
|
66
|
+
found = [
|
|
67
|
+
(n, m.group(1))
|
|
68
|
+
for n, line in enumerate(source.splitlines(), start=1)
|
|
69
|
+
if (m := _RE_LANES.match(line))
|
|
70
|
+
]
|
|
71
|
+
if not found:
|
|
72
|
+
return []
|
|
73
|
+
line_no, raw = found[0]
|
|
74
|
+
lanes: Any = None
|
|
75
|
+
message: str | None
|
|
76
|
+
if len(found) > 1:
|
|
77
|
+
line_no, message = found[1][0], "@lanes may be declared once"
|
|
78
|
+
else:
|
|
79
|
+
try:
|
|
80
|
+
lanes = json.loads(raw)
|
|
81
|
+
except json.JSONDecodeError as e:
|
|
82
|
+
message = f"Invalid @lanes JSON: {e}"
|
|
83
|
+
else:
|
|
84
|
+
message = _lanes_problem(lanes)
|
|
85
|
+
if message:
|
|
86
|
+
return ValidationError(stage="profile", message=message, line=line_no)
|
|
87
|
+
return cast(list[str], lanes)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _lanes_problem(lanes: Any) -> str | None:
|
|
91
|
+
if (
|
|
92
|
+
not isinstance(lanes, list)
|
|
93
|
+
or not lanes
|
|
94
|
+
or not all(isinstance(s, str) for s in lanes)
|
|
95
|
+
):
|
|
96
|
+
return "@lanes must be a non-empty JSON list of strings"
|
|
97
|
+
unknown = sorted(set(lanes) - KNOWN_LANES)
|
|
98
|
+
if unknown:
|
|
99
|
+
return f"Unknown lane(s) {unknown}; known: {sorted(KNOWN_LANES)}"
|
|
100
|
+
if len(set(lanes)) != len(lanes):
|
|
101
|
+
return "@lanes repeats a lane"
|
|
102
|
+
return None
|
|
55
103
|
|
|
56
104
|
|
|
57
105
|
def validate(
|
|
@@ -130,6 +178,9 @@ def validate_fast( # noqa: PLR0911
|
|
|
130
178
|
|
|
131
179
|
constants = extract_constants(algo)
|
|
132
180
|
invariants = [d for d in extract_defines(algo) if d not in constants]
|
|
181
|
+
lanes = extract_lanes(source)
|
|
182
|
+
if isinstance(lanes, ValidationError):
|
|
183
|
+
return [lanes]
|
|
133
184
|
|
|
134
185
|
# Stage 3: Graph check. Tell the reachability check that
|
|
135
186
|
# ``interrupt`` nodes count as termination — a chat-shape
|
|
@@ -177,6 +228,7 @@ def validate_fast( # noqa: PLR0911
|
|
|
177
228
|
routing=routing,
|
|
178
229
|
invariants=invariants,
|
|
179
230
|
constants=constants,
|
|
231
|
+
lanes=lanes,
|
|
180
232
|
)
|
|
181
233
|
|
|
182
234
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|