mas-lab-component-controller 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.
- mas/lab/controller/__init__.py +7 -0
- mas/lab/controller/__main__.py +5 -0
- mas/lab/controller/agent_chat.py +226 -0
- mas/lab/controller/api.py +364 -0
- mas/lab/controller/artifact_discovery.py +107 -0
- mas/lab/controller/artifacts.py +112 -0
- mas/lab/controller/cli.py +493 -0
- mas/lab/controller/client.py +271 -0
- mas/lab/controller/config.py +75 -0
- mas/lab/controller/constants.py +30 -0
- mas/lab/controller/daemon.py +190 -0
- mas/lab/controller/deps.py +172 -0
- mas/lab/controller/fastapi_app.py +166 -0
- mas/lab/controller/http_server.py +243 -0
- mas/lab/controller/io_capture.py +55 -0
- mas/lab/controller/item_service.py +625 -0
- mas/lab/controller/job_store.py +209 -0
- mas/lab/controller/jobs.py +375 -0
- mas/lab/controller/kinds.py +149 -0
- mas/lab/controller/lab_registry.py +1076 -0
- mas/lab/controller/manifest_layout.py +832 -0
- mas/lab/controller/manifest_store.py +214 -0
- mas/lab/controller/manifest_validation.py +90 -0
- mas/lab/controller/models.py +215 -0
- mas/lab/controller/path_safety.py +68 -0
- mas/lab/controller/pipeline_validation.py +79 -0
- mas/lab/controller/registry.py +83 -0
- mas/lab/controller/routes/__init__.py +82 -0
- mas/lab/controller/routes/_api.py +15 -0
- mas/lab/controller/routes/apps.py +190 -0
- mas/lab/controller/routes/artifacts.py +37 -0
- mas/lab/controller/routes/benchmark.py +307 -0
- mas/lab/controller/routes/choices.py +141 -0
- mas/lab/controller/routes/datasets.py +91 -0
- mas/lab/controller/routes/design_patterns.py +121 -0
- mas/lab/controller/routes/eval_output.py +31 -0
- mas/lab/controller/routes/experiments.py +194 -0
- mas/lab/controller/routes/health.py +178 -0
- mas/lab/controller/routes/items.py +187 -0
- mas/lab/controller/routes/jobs.py +107 -0
- mas/lab/controller/routes/libraries.py +144 -0
- mas/lab/controller/routes/overlays.py +164 -0
- mas/lab/controller/routes/pipelines.py +139 -0
- mas/lab/controller/routes/registry.py +40 -0
- mas/lab/controller/routes/run.py +215 -0
- mas/lab/controller/routes/runs.py +392 -0
- mas/lab/controller/routes/scopes.py +139 -0
- mas/lab/controller/routes/uploads.py +24 -0
- mas/lab/controller/routes/validate.py +36 -0
- mas/lab/controller/run_bridge.py +276 -0
- mas/lab/controller/run_store.py +190 -0
- mas/lab/controller/schema_registry.py +109 -0
- mas/lab/controller/schemas/pipeline-step-types-post.json +764 -0
- mas/lab/controller/schemas/pipeline-step-types-pre.json +35 -0
- mas/lab/controller/scope_catalog.py +266 -0
- mas/lab/controller/scope_service.py +134 -0
- mas/lab/controller/sessions.py +63 -0
- mas/lab/controller/settings.py +65 -0
- mas/lab/controller/ui_stream/__init__.py +13 -0
- mas/lab/controller/ui_stream/app.py +2147 -0
- mas/lab/controller/ui_stream/artefact_bind.py +308 -0
- mas/lab/controller/ui_stream/auth.py +136 -0
- mas/lab/controller/ui_stream/bus.py +156 -0
- mas/lab/controller/ui_stream/export.py +400 -0
- mas/lab/controller/ui_stream/governance.py +141 -0
- mas/lab/controller/ui_stream/ingest.py +300 -0
- mas/lab/controller/ui_stream/inspect.py +55 -0
- mas/lab/controller/ui_stream/layouts.py +161 -0
- mas/lab/controller/ui_stream/metrics.py +274 -0
- mas/lab/controller/ui_stream/plot_folds.py +609 -0
- mas/lab/controller/ui_stream/plugin_registry.py +139 -0
- mas/lab/controller/ui_stream/refs.py +259 -0
- mas/lab/controller/ui_stream/replay.py +236 -0
- mas/lab/controller/ui_stream/replay_cli.py +707 -0
- mas/lab/controller/ui_stream/run_index.py +337 -0
- mas/lab/controller/ui_stream/spec_load.py +48 -0
- mas/lab/controller/ui_stream/spec_snapshot.py +313 -0
- mas/lab/controller/ui_stream/stream.py +95 -0
- mas/lab/controller/ui_stream/streams.py +42 -0
- mas/lab/controller/ui_stream/timeline.py +183 -0
- mas/lab/controller/ui_stream/ui_trace.py +258 -0
- mas/lab/controller/uploads.py +61 -0
- mas/lab/controller/worker_model.py +137 -0
- mas/lab/controller/workers.py +296 -0
- mas/lab/controller/workspace_library.py +70 -0
- mas_lab_component_controller-0.2.0.dist-info/METADATA +21 -0
- mas_lab_component_controller-0.2.0.dist-info/RECORD +89 -0
- mas_lab_component_controller-0.2.0.dist-info/WHEEL +4 -0
- mas_lab_component_controller-0.2.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Worker model types."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import enum
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WorkerKind(str, enum.Enum):
|
|
14
|
+
APPLICATION = "application"
|
|
15
|
+
BENCHMARK = "benchmark"
|
|
16
|
+
EXPERIMENT = "experiment"
|
|
17
|
+
PIPELINE = "pipeline"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkerStatus(str, enum.Enum):
|
|
21
|
+
PENDING = "pending"
|
|
22
|
+
RUNNING = "running"
|
|
23
|
+
COMPLETED = "completed"
|
|
24
|
+
FAILED = "failed"
|
|
25
|
+
CANCELLED = "cancelled"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class WorkerRecord:
|
|
30
|
+
"""In-memory worker state owned by the controller daemon."""
|
|
31
|
+
|
|
32
|
+
id: str
|
|
33
|
+
kind: WorkerKind
|
|
34
|
+
status: WorkerStatus = WorkerStatus.PENDING
|
|
35
|
+
created_at: float = field(default_factory=time.time)
|
|
36
|
+
started_at: Optional[float] = None
|
|
37
|
+
finished_at: Optional[float] = None
|
|
38
|
+
exit_code: Optional[int] = None
|
|
39
|
+
error: Optional[str] = None
|
|
40
|
+
command: str = ""
|
|
41
|
+
endpoint: str = ""
|
|
42
|
+
spec: Dict[str, Any] = field(default_factory=dict)
|
|
43
|
+
stdout: List[str] = field(default_factory=list)
|
|
44
|
+
stderr: List[str] = field(default_factory=list)
|
|
45
|
+
stdout_text: str = ""
|
|
46
|
+
stderr_text: str = ""
|
|
47
|
+
result: Any = None
|
|
48
|
+
parent_id: Optional[str] = None
|
|
49
|
+
pid: Optional[int] = None
|
|
50
|
+
|
|
51
|
+
def append_stdout(self, line: str) -> None:
|
|
52
|
+
self.stdout.append(line)
|
|
53
|
+
|
|
54
|
+
def append_stderr(self, line: str) -> None:
|
|
55
|
+
self.stderr.append(line)
|
|
56
|
+
|
|
57
|
+
def append_stdout_chunk(self, text: str) -> None:
|
|
58
|
+
self.stdout_text += text
|
|
59
|
+
|
|
60
|
+
def append_stderr_chunk(self, text: str) -> None:
|
|
61
|
+
self.stderr_text += text
|
|
62
|
+
|
|
63
|
+
def combined_stdout(self) -> str:
|
|
64
|
+
parts = []
|
|
65
|
+
if self.stdout:
|
|
66
|
+
parts.append("\n".join(self.stdout))
|
|
67
|
+
if self.stdout_text:
|
|
68
|
+
parts.append(self.stdout_text)
|
|
69
|
+
return "\n".join(parts)
|
|
70
|
+
|
|
71
|
+
def combined_stderr(self) -> str:
|
|
72
|
+
parts = []
|
|
73
|
+
if self.stderr:
|
|
74
|
+
parts.append("\n".join(self.stderr))
|
|
75
|
+
if self.stderr_text:
|
|
76
|
+
parts.append(self.stderr_text)
|
|
77
|
+
return "\n".join(parts)
|
|
78
|
+
|
|
79
|
+
def to_job_summary(self) -> Dict[str, Any]:
|
|
80
|
+
origin = _ui_origin(self.spec)
|
|
81
|
+
summary = {
|
|
82
|
+
"id": self.id,
|
|
83
|
+
"kind": self.kind.value,
|
|
84
|
+
"endpoint": self.endpoint,
|
|
85
|
+
"command": self.command,
|
|
86
|
+
"status": self._job_status(),
|
|
87
|
+
"created_at": _iso(self.created_at),
|
|
88
|
+
"started_at": _iso(self.started_at),
|
|
89
|
+
"finished_at": _iso(self.finished_at),
|
|
90
|
+
"pid": self.pid,
|
|
91
|
+
"exit_code": self.exit_code,
|
|
92
|
+
"origin": origin,
|
|
93
|
+
}
|
|
94
|
+
if self.error:
|
|
95
|
+
summary["error"] = self.error
|
|
96
|
+
return summary
|
|
97
|
+
|
|
98
|
+
def to_job_detail(self) -> Dict[str, Any]:
|
|
99
|
+
detail = self.to_job_summary()
|
|
100
|
+
detail.update(
|
|
101
|
+
{
|
|
102
|
+
"stdout": self.combined_stdout(),
|
|
103
|
+
"stderr": self.combined_stderr(),
|
|
104
|
+
"error": self.error,
|
|
105
|
+
"request_body": self.spec,
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
return detail
|
|
109
|
+
|
|
110
|
+
def _job_status(self) -> str:
|
|
111
|
+
mapping = {
|
|
112
|
+
WorkerStatus.PENDING: "pending",
|
|
113
|
+
WorkerStatus.RUNNING: "running",
|
|
114
|
+
WorkerStatus.COMPLETED: "completed",
|
|
115
|
+
WorkerStatus.FAILED: "failed",
|
|
116
|
+
WorkerStatus.CANCELLED: "cancelled",
|
|
117
|
+
}
|
|
118
|
+
return mapping.get(self.status, "running")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _ui_origin(spec: Dict[str, Any] | None) -> str | None:
|
|
122
|
+
ref = (spec or {}).get("demo_ui_ref") if isinstance(spec, dict) else None
|
|
123
|
+
if not isinstance(ref, dict) or not ref:
|
|
124
|
+
return None
|
|
125
|
+
return str(ref.get("origin") or "demo-ui")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def new_worker_id(prefix: str = "w") -> str:
|
|
129
|
+
return f"{prefix}-{uuid.uuid4().hex[:12]}"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _iso(ts: Optional[float]) -> Optional[str]:
|
|
133
|
+
if ts is None:
|
|
134
|
+
return None
|
|
135
|
+
from datetime import datetime, timezone
|
|
136
|
+
|
|
137
|
+
return datetime.fromtimestamp(ts, tz=timezone.utc).isoformat()
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Worker implementations."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Callable
|
|
14
|
+
|
|
15
|
+
from mas.lab.controller.io_capture import capture_worker_io
|
|
16
|
+
from mas.lab.controller.registry import WorkerRegistry
|
|
17
|
+
from mas.lab.controller.worker_model import WorkerKind, WorkerRecord, WorkerStatus
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WorkerRunner:
|
|
23
|
+
"""Runs worker callables in background threads."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, registry: WorkerRegistry) -> None:
|
|
26
|
+
self.registry = registry
|
|
27
|
+
self._cancel_flags: dict[str, threading.Event] = {}
|
|
28
|
+
|
|
29
|
+
def submit(self, record: WorkerRecord, fn: Callable[[], Any]) -> None:
|
|
30
|
+
cancel = threading.Event()
|
|
31
|
+
self._cancel_flags[record.id] = cancel
|
|
32
|
+
|
|
33
|
+
def _run() -> None:
|
|
34
|
+
record.status = WorkerStatus.RUNNING
|
|
35
|
+
record.started_at = time.time()
|
|
36
|
+
record.pid = threading.get_ident()
|
|
37
|
+
try:
|
|
38
|
+
if cancel.is_set():
|
|
39
|
+
record.status = WorkerStatus.CANCELLED
|
|
40
|
+
return
|
|
41
|
+
result = fn()
|
|
42
|
+
if cancel.is_set():
|
|
43
|
+
record.status = WorkerStatus.CANCELLED
|
|
44
|
+
elif result is False:
|
|
45
|
+
record.status = WorkerStatus.FAILED
|
|
46
|
+
record.exit_code = 1
|
|
47
|
+
record.error = "worker returned failure"
|
|
48
|
+
else:
|
|
49
|
+
record.status = WorkerStatus.COMPLETED
|
|
50
|
+
record.exit_code = 0
|
|
51
|
+
record.result = result
|
|
52
|
+
except Exception as exc:
|
|
53
|
+
logger.exception("Worker %s failed", record.id)
|
|
54
|
+
record.status = WorkerStatus.FAILED
|
|
55
|
+
record.exit_code = 1
|
|
56
|
+
record.error = str(exc)
|
|
57
|
+
record.append_stderr(str(exc))
|
|
58
|
+
finally:
|
|
59
|
+
record.finished_at = time.time()
|
|
60
|
+
self._cancel_flags.pop(record.id, None)
|
|
61
|
+
|
|
62
|
+
threading.Thread(target=_run, daemon=True, name=f"worker-{record.id}").start()
|
|
63
|
+
|
|
64
|
+
def cancel(self, worker_id: str) -> bool:
|
|
65
|
+
event = self._cancel_flags.get(worker_id)
|
|
66
|
+
if event is not None:
|
|
67
|
+
event.set()
|
|
68
|
+
return self.registry.cancel(worker_id)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run_benchmark_worker(registry: WorkerRegistry, runner: "WorkerRunner", spec: dict) -> WorkerRecord:
|
|
72
|
+
experiment_yaml = Path(spec["experiment_yaml"])
|
|
73
|
+
cmd_parts = ["mas-lab", "benchmark", "run", str(experiment_yaml)]
|
|
74
|
+
for flag in ("force", "dry_run", "single_run", "force_lock"):
|
|
75
|
+
if spec.get(flag):
|
|
76
|
+
cmd_parts.append(f"--{flag.replace('_', '-')}")
|
|
77
|
+
record = registry.create(
|
|
78
|
+
WorkerKind.BENCHMARK,
|
|
79
|
+
spec=spec,
|
|
80
|
+
command=" ".join(cmd_parts),
|
|
81
|
+
endpoint="/api/benchmark/run",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def _execute() -> bool:
|
|
85
|
+
import traceback
|
|
86
|
+
|
|
87
|
+
from mas.lab.benchmark.engine import BenchmarkRunOptions, run_benchmark
|
|
88
|
+
|
|
89
|
+
with capture_worker_io(record):
|
|
90
|
+
try:
|
|
91
|
+
ok = asyncio.run(
|
|
92
|
+
run_benchmark(
|
|
93
|
+
experiment_yaml=experiment_yaml,
|
|
94
|
+
options=BenchmarkRunOptions.from_spec(spec),
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
except Exception:
|
|
98
|
+
# Print the full traceback into the captured stream, then re-raise
|
|
99
|
+
# so the CLI shows the real cause, not just "benchmark run failed".
|
|
100
|
+
traceback.print_exc()
|
|
101
|
+
raise
|
|
102
|
+
if not ok:
|
|
103
|
+
raise RuntimeError(
|
|
104
|
+
"benchmark run failed — see the logged error above for the cause"
|
|
105
|
+
)
|
|
106
|
+
return ok
|
|
107
|
+
|
|
108
|
+
runner.submit(record, _execute)
|
|
109
|
+
return record
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _bind_demo_ui_env(spec: dict) -> dict[str, str | None]:
|
|
113
|
+
import json
|
|
114
|
+
import os
|
|
115
|
+
|
|
116
|
+
keys = (
|
|
117
|
+
"MAS_DEMO_UI_INGEST_URL",
|
|
118
|
+
"MAS_DEMO_UI_INGEST_KINDS",
|
|
119
|
+
"MAS_DEMO_UI_REF",
|
|
120
|
+
"MAS_DEMO_UI_PROMPT",
|
|
121
|
+
"MAS_INFRA_REFS",
|
|
122
|
+
"MAS_DEMO_UI_SCENARIO_INFRA",
|
|
123
|
+
"MAS_DEMO_UI_SCENARIO_OVERLAYS",
|
|
124
|
+
"MAS_LLM_CACHE",
|
|
125
|
+
)
|
|
126
|
+
previous = {key: os.environ.get(key) for key in keys}
|
|
127
|
+
ingest = spec.get("demo_ui_ingest_url")
|
|
128
|
+
if ingest:
|
|
129
|
+
os.environ["MAS_DEMO_UI_INGEST_URL"] = str(ingest)
|
|
130
|
+
os.environ.setdefault("MAS_DEMO_UI_INGEST_KINDS", "all")
|
|
131
|
+
ref = spec.get("demo_ui_ref")
|
|
132
|
+
if ref:
|
|
133
|
+
os.environ["MAS_DEMO_UI_REF"] = json.dumps(ref)
|
|
134
|
+
query = spec.get("query")
|
|
135
|
+
if query:
|
|
136
|
+
os.environ["MAS_DEMO_UI_PROMPT"] = str(query)
|
|
137
|
+
refs = spec.get("infra_refs") or []
|
|
138
|
+
if refs:
|
|
139
|
+
os.environ["MAS_INFRA_REFS"] = ",".join(str(item) for item in refs if item)
|
|
140
|
+
elif "MAS_INFRA_REFS" in os.environ and not spec.get("keep_process_infra"):
|
|
141
|
+
os.environ.pop("MAS_INFRA_REFS", None)
|
|
142
|
+
mapping = spec.get("scenario_infra_refs") or {}
|
|
143
|
+
if mapping:
|
|
144
|
+
os.environ["MAS_DEMO_UI_SCENARIO_INFRA"] = json.dumps(mapping)
|
|
145
|
+
elif "MAS_DEMO_UI_SCENARIO_INFRA" in os.environ and not spec.get("keep_process_infra"):
|
|
146
|
+
os.environ.pop("MAS_DEMO_UI_SCENARIO_INFRA", None)
|
|
147
|
+
overlays = spec.get("scenario_overlays") or {}
|
|
148
|
+
if overlays:
|
|
149
|
+
os.environ["MAS_DEMO_UI_SCENARIO_OVERLAYS"] = json.dumps(overlays)
|
|
150
|
+
elif "MAS_DEMO_UI_SCENARIO_OVERLAYS" in os.environ and not spec.get("keep_process_infra"):
|
|
151
|
+
os.environ.pop("MAS_DEMO_UI_SCENARIO_OVERLAYS", None)
|
|
152
|
+
cwd = spec.get("cwd")
|
|
153
|
+
if cwd and not os.environ.get("MAS_LLM_CACHE"):
|
|
154
|
+
eval_cache = Path(cwd) / "infra" / ".cache" / "eval.llm-cache.json"
|
|
155
|
+
os.environ["MAS_LLM_CACHE"] = str(eval_cache)
|
|
156
|
+
return previous
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _restore_env(previous: dict[str, str | None]) -> None:
|
|
160
|
+
import os
|
|
161
|
+
|
|
162
|
+
for key, value in previous.items():
|
|
163
|
+
if value is None:
|
|
164
|
+
os.environ.pop(key, None)
|
|
165
|
+
else:
|
|
166
|
+
os.environ[key] = value
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def run_experiment_worker(registry: WorkerRegistry, runner: "WorkerRunner", spec: dict) -> WorkerRecord:
|
|
170
|
+
"""In-process experiment worker: same engine as a benchmark, live ingest, no stdout dump."""
|
|
171
|
+
experiment_yaml = Path(spec["experiment_yaml"])
|
|
172
|
+
record = registry.create(
|
|
173
|
+
WorkerKind.EXPERIMENT,
|
|
174
|
+
spec=spec,
|
|
175
|
+
command=spec.get("command") or f"mas-lab experiment run {experiment_yaml}",
|
|
176
|
+
endpoint=spec.get("endpoint", "/api/experiment/run"),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def _execute() -> bool:
|
|
180
|
+
import traceback
|
|
181
|
+
|
|
182
|
+
from mas.lab.benchmark.engine import BenchmarkRunOptions, run_benchmark
|
|
183
|
+
|
|
184
|
+
previous = _bind_demo_ui_env(spec)
|
|
185
|
+
live_spec = {**spec, "progress": False}
|
|
186
|
+
cwd = spec.get("cwd")
|
|
187
|
+
prev_cwd = os.getcwd()
|
|
188
|
+
with capture_worker_io(record):
|
|
189
|
+
try:
|
|
190
|
+
if cwd:
|
|
191
|
+
os.chdir(str(cwd))
|
|
192
|
+
ok = asyncio.run(
|
|
193
|
+
run_benchmark(
|
|
194
|
+
experiment_yaml=experiment_yaml,
|
|
195
|
+
options=BenchmarkRunOptions.from_spec(live_spec),
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
except Exception:
|
|
199
|
+
traceback.print_exc()
|
|
200
|
+
raise
|
|
201
|
+
finally:
|
|
202
|
+
os.chdir(prev_cwd)
|
|
203
|
+
_restore_env(previous)
|
|
204
|
+
if not ok:
|
|
205
|
+
raise RuntimeError(
|
|
206
|
+
"experiment run failed — see the logged error above for the cause"
|
|
207
|
+
)
|
|
208
|
+
return ok
|
|
209
|
+
|
|
210
|
+
runner.submit(record, _execute)
|
|
211
|
+
return record
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def run_application_worker(registry: WorkerRegistry, runner: "WorkerRunner", spec: dict) -> WorkerRecord:
|
|
215
|
+
record = registry.create(
|
|
216
|
+
WorkerKind.APPLICATION,
|
|
217
|
+
spec=spec,
|
|
218
|
+
command=spec.get("command", "application run"),
|
|
219
|
+
endpoint=spec.get("endpoint", "/api/run"),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _execute() -> dict:
|
|
223
|
+
from mas.lab.runners.constants import DEFAULT_LAB_RUNNER_ID, normalize_runner_id
|
|
224
|
+
from mas.lab.runners.factory import RunnerFactory
|
|
225
|
+
|
|
226
|
+
runner_id = normalize_runner_id(spec.get("runner_id", DEFAULT_LAB_RUNNER_ID))
|
|
227
|
+
app_runner = RunnerFactory.get(runner_id)
|
|
228
|
+
output_dir = Path(spec.get("output_dir") or tempfile.mkdtemp(prefix="mas-lab-run-"))
|
|
229
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
230
|
+
result = app_runner.run(
|
|
231
|
+
spec["prompt"],
|
|
232
|
+
config=spec.get("config") or {},
|
|
233
|
+
spec_path=Path(spec["spec_path"]),
|
|
234
|
+
flavour=spec.get("flavour"),
|
|
235
|
+
output_dir=output_dir,
|
|
236
|
+
turns=spec.get("turns"),
|
|
237
|
+
session_id=spec.get("session_id"),
|
|
238
|
+
memory_seeds=spec.get("memory_seeds"),
|
|
239
|
+
run_seed=spec.get("run_seed", 0),
|
|
240
|
+
emulation_plugins=spec.get("emulation_plugins"),
|
|
241
|
+
)
|
|
242
|
+
return {
|
|
243
|
+
"content": result.content,
|
|
244
|
+
"status": result.status,
|
|
245
|
+
"error": result.error,
|
|
246
|
+
"output_dir": str(output_dir),
|
|
247
|
+
"metadata": result.metadata,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
runner.submit(record, _execute)
|
|
251
|
+
return record
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def run_pipeline_worker(registry: WorkerRegistry, runner: "WorkerRunner", spec: dict) -> WorkerRecord:
|
|
255
|
+
record = registry.create(
|
|
256
|
+
WorkerKind.PIPELINE,
|
|
257
|
+
spec=spec,
|
|
258
|
+
command=spec.get("command", "pipeline run"),
|
|
259
|
+
endpoint="/api/pipeline/run",
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def _execute() -> Any:
|
|
263
|
+
import asyncio
|
|
264
|
+
from pathlib import Path
|
|
265
|
+
|
|
266
|
+
from mas.lab.benchmark.pipeline import Pipeline, PipelineExecutor
|
|
267
|
+
from mas.lab.benchmark.pipeline.cli import _load_lab_custom_steps
|
|
268
|
+
from mas.lab.benchmark.pipeline.config import PipelineEngineSpec
|
|
269
|
+
from mas.lab.benchmark.pipeline.channel import install_channel
|
|
270
|
+
|
|
271
|
+
pipeline_yaml = Path(spec["pipeline_yaml"])
|
|
272
|
+
_load_lab_custom_steps(pipeline_yaml.resolve())
|
|
273
|
+
try:
|
|
274
|
+
install_channel(PipelineEngineSpec())
|
|
275
|
+
except Exception:
|
|
276
|
+
pass
|
|
277
|
+
pipeline = Pipeline.from_yaml(pipeline_yaml)
|
|
278
|
+
executor = PipelineExecutor(
|
|
279
|
+
pipeline,
|
|
280
|
+
output_dir=Path(spec["output_dir"]) if spec.get("output_dir") else None,
|
|
281
|
+
)
|
|
282
|
+
result = asyncio.run(
|
|
283
|
+
executor.run(
|
|
284
|
+
steps=spec.get("only"),
|
|
285
|
+
force_rerun=spec.get("force"),
|
|
286
|
+
dry_run=spec.get("dry_run", False),
|
|
287
|
+
parallel=spec.get("parallel", True),
|
|
288
|
+
template_vars=spec.get("variables") or {},
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
if not result.success:
|
|
292
|
+
raise RuntimeError(f"pipeline failed — {result.summary()}")
|
|
293
|
+
return {"success": True, "summary": result.summary()}
|
|
294
|
+
|
|
295
|
+
runner.submit(record, _execute)
|
|
296
|
+
return record
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Writable controller workspace: the place new apps and datasets land."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from mas.lab.controller.kinds import LIBRARY_BASE_KINDS
|
|
10
|
+
from mas.lab.controller.scope_catalog import Scope
|
|
11
|
+
from mas.runtime.constants import LIBRARY_MANIFEST_FILENAME
|
|
12
|
+
|
|
13
|
+
WORKSPACE_SLUG = "workspace"
|
|
14
|
+
WORKSPACE_SCOPE_ID = f"library:{WORKSPACE_SLUG}"
|
|
15
|
+
|
|
16
|
+
_MANIFEST = """\
|
|
17
|
+
apiVersion: mas/v1
|
|
18
|
+
kind: Library
|
|
19
|
+
metadata:
|
|
20
|
+
name: workspace
|
|
21
|
+
description: Controller workspace for new applications, overlays, and datasets
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
_root: Path | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def init_workspace_library(parent: Path) -> Path:
|
|
28
|
+
"""Create ``parent/library`` if needed and remember it for the catalog.
|
|
29
|
+
|
|
30
|
+
*parent* is the project workspace (``MAS_WORKSPACE_ROOT`` /
|
|
31
|
+
``mas-lab serve --workspace``) when one is pinned, otherwise the
|
|
32
|
+
controller ``--state-dir``.
|
|
33
|
+
"""
|
|
34
|
+
global _root
|
|
35
|
+
root = Path(parent) / "library"
|
|
36
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
(root / "apps").mkdir(exist_ok=True)
|
|
38
|
+
(root / "overlays").mkdir(exist_ok=True)
|
|
39
|
+
(root / "datasets").mkdir(exist_ok=True)
|
|
40
|
+
manifest = root / LIBRARY_MANIFEST_FILENAME
|
|
41
|
+
if not manifest.is_file():
|
|
42
|
+
manifest.write_text(_MANIFEST, encoding="utf-8")
|
|
43
|
+
_root = root
|
|
44
|
+
return root
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def reset_workspace_library() -> None:
|
|
48
|
+
"""Clear the process-wide workspace pin (tests / apps without ``state_dir``)."""
|
|
49
|
+
global _root
|
|
50
|
+
_root = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def workspace_root() -> Path | None:
|
|
54
|
+
return _root
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def workspace_scope() -> Scope | None:
|
|
58
|
+
if _root is None:
|
|
59
|
+
return None
|
|
60
|
+
return Scope(
|
|
61
|
+
id=WORKSPACE_SCOPE_ID,
|
|
62
|
+
kind="library",
|
|
63
|
+
name=WORKSPACE_SLUG,
|
|
64
|
+
title="Workspace",
|
|
65
|
+
description="Controller workspace for new applications, overlays, and datasets",
|
|
66
|
+
origin="workspace",
|
|
67
|
+
writable=True,
|
|
68
|
+
root=_root.resolve(),
|
|
69
|
+
kinds=list(LIBRARY_BASE_KINDS),
|
|
70
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mas-lab-component-controller
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: MAS Lab controller daemon — workers, IPC, HTTP API for CLI and UI.
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: click>=8.0
|
|
8
|
+
Requires-Dist: fastapi>=0.110
|
|
9
|
+
Requires-Dist: mas-lab-component-bench
|
|
10
|
+
Requires-Dist: mas-lab-component-core
|
|
11
|
+
Requires-Dist: mas-lab-ctl
|
|
12
|
+
Requires-Dist: mas-lab-runtime
|
|
13
|
+
Requires-Dist: pydantic>=2.0
|
|
14
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
15
|
+
Requires-Dist: pyyaml>=6.0
|
|
16
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=9.0; extra == 'dev'
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
mas/lab/controller/__init__.py,sha256=XLifiSasGCaun4Id8QUcaLEmwZJdDskRtOvfIcg2zzQ,347
|
|
2
|
+
mas/lab/controller/__main__.py,sha256=8KchWhPetfnGEWp_WyUAQnuvXcfO35AF-vSo2f71Tlw,290
|
|
3
|
+
mas/lab/controller/agent_chat.py,sha256=smM3NrvQoKc17cBfRCuOxum0MemG0TgfTFs2any9QcY,6743
|
|
4
|
+
mas/lab/controller/api.py,sha256=aM3Kopab5nYkImfQqK1XzzIHXyFykAh44eaY5bSbvfY,15008
|
|
5
|
+
mas/lab/controller/artifact_discovery.py,sha256=epfftnYyTauQVcQ12Oin8cpG8jnkbCX8xk3la2z0ZLU,4046
|
|
6
|
+
mas/lab/controller/artifacts.py,sha256=Q1Tj8z0yqYoJjDyZt1b1_G5zTxP3i-y2I1_HffgtI5k,3395
|
|
7
|
+
mas/lab/controller/cli.py,sha256=aR0q9YDVG_Q5PZVq3nCCoZ6DQ5jYf53X49bppANKn6A,17126
|
|
8
|
+
mas/lab/controller/client.py,sha256=ok9nl0ivoyeS1fv79WEayOhBthZdY0xUKS9basMmLb0,9226
|
|
9
|
+
mas/lab/controller/config.py,sha256=8j7gbMAuj6-CvXIWm41enEXY7FG5RltsBSe8cwsdQgc,2659
|
|
10
|
+
mas/lab/controller/constants.py,sha256=EVrABwkEiFWqPThSqI8us50bNqkIQRWA1bgZ40H_l-g,838
|
|
11
|
+
mas/lab/controller/daemon.py,sha256=UE5miB4W-jywl6FGGFcFt5BLr3PUYXc_2Qjpi_pmrTA,6813
|
|
12
|
+
mas/lab/controller/deps.py,sha256=ZDLENKw51wKcUBFgMR2dNmsPywctfnbOr7Mozx1JFfM,5612
|
|
13
|
+
mas/lab/controller/fastapi_app.py,sha256=cSggjH-88OkPIDc8rEPf_RI2HNG0oaR1_hYC6gVEY1U,5518
|
|
14
|
+
mas/lab/controller/http_server.py,sha256=d8cXAFtn8E_LmTn_qRLu2NDUoMrZ1NzT5rpaFESdQUk,11020
|
|
15
|
+
mas/lab/controller/io_capture.py,sha256=qoftW6x1jmhfgpxaWlnEWhdNOqVLxBIAAXDDUzLy3cA,1726
|
|
16
|
+
mas/lab/controller/item_service.py,sha256=SGV4DUTAPgQilLA8J2c4gwVIonGduu1pAGDG7hHmLQQ,21109
|
|
17
|
+
mas/lab/controller/job_store.py,sha256=Hi-fIEAWHvl5y2O2W8ntenpVaJ52KKql5R3SjhpjVAY,6042
|
|
18
|
+
mas/lab/controller/jobs.py,sha256=uS2i-GuordSy0tF1IdYgyzILYxiJ6rJhsn5uIi9vKO4,10889
|
|
19
|
+
mas/lab/controller/kinds.py,sha256=zWtiDh0NKELXY9Ycz-VEnojV_5rSe4vmQL9B2nybHxA,4034
|
|
20
|
+
mas/lab/controller/lab_registry.py,sha256=9ykKAGq1VJAeG6cEEYNfNLq90kjqzmM-YR5vLR6zkg0,43761
|
|
21
|
+
mas/lab/controller/manifest_layout.py,sha256=pF2b3_YKgLXT7aVWHjnneqZUn7uhbpTM9rTbFOZwMog,28422
|
|
22
|
+
mas/lab/controller/manifest_store.py,sha256=LltRk-jr9HTKhfBC_MROnFK0RNmxsMnE55DrLsI5lCk,8464
|
|
23
|
+
mas/lab/controller/manifest_validation.py,sha256=0es70b2SwkkVDMpqqr2nU3TVs_hZQ2y1_xk_T_hf_VU,2705
|
|
24
|
+
mas/lab/controller/models.py,sha256=9VoM1RNiNmO4STu_jl01PTVWsrWE4EFcErM0_kZt7AA,9052
|
|
25
|
+
mas/lab/controller/path_safety.py,sha256=16iP0_RTSoJen40yGHfHdRxITtfulDX4zAzYI0Sjz4M,2390
|
|
26
|
+
mas/lab/controller/pipeline_validation.py,sha256=tbXLukZ95MSUTzsPHhUHhbqmJvoioPqC06q6VSKOOec,2714
|
|
27
|
+
mas/lab/controller/registry.py,sha256=yzD4AufuesCYFujz62IXm-d28t7eznpM0-Oqi8J5oUg,2773
|
|
28
|
+
mas/lab/controller/run_bridge.py,sha256=IpHk5w_EH3bG3ueExq3k3Zx44_u3h9wYxS5JUzf1--4,8667
|
|
29
|
+
mas/lab/controller/run_store.py,sha256=jT2m948lrW58sBo8u4ugIYT-iblGLBbbv0X28iqn4Zs,5521
|
|
30
|
+
mas/lab/controller/schema_registry.py,sha256=wWlFARQGPwl1U7fZgZoEqLUPybWk5kpfgUF4HZppTdQ,4726
|
|
31
|
+
mas/lab/controller/scope_catalog.py,sha256=RIVSb9JXydMzMIYcqhVlnNhsbjcP7LbDekNTQq6kMrU,9690
|
|
32
|
+
mas/lab/controller/scope_service.py,sha256=W6thTeKnmiSN9eJC6y5dZPFyCYdMk0N-dGGEGhvo60E,4559
|
|
33
|
+
mas/lab/controller/sessions.py,sha256=q5Ls-K32CngLEPjn9L2PVCQJK5IxJvmbvbxw1HesiOA,2129
|
|
34
|
+
mas/lab/controller/settings.py,sha256=heAbkQ7MiPI2HtxIR4Y156g8t6lQqBKCnBtsYuhnMAo,1860
|
|
35
|
+
mas/lab/controller/uploads.py,sha256=q2ZG3IF6CA8A6A1hnv4XZRxLIkP5bZJsOtul3hU0n3I,1696
|
|
36
|
+
mas/lab/controller/worker_model.py,sha256=6baEqc59May2bPM7EtDreV3dDB2DM4XqXw4UGxWxtFc,4019
|
|
37
|
+
mas/lab/controller/workers.py,sha256=gkWdQlti6h01oMToPI423_X5CZga-f80mS_8Qp8emwA,10715
|
|
38
|
+
mas/lab/controller/workspace_library.py,sha256=GlbrlzF4FmKRn2G2YAf2ahoA4Ij0UBo6bu9_hvTIrXU,2018
|
|
39
|
+
mas/lab/controller/routes/__init__.py,sha256=9cFo_KpfmW9ualwAmhXMLtcwMtWpMUm3ddn6VpgsHc4,2377
|
|
40
|
+
mas/lab/controller/routes/_api.py,sha256=KzyTu3OV3d9p9VITIeznXk71xaAp-J8eO4wUVSMrMfo,483
|
|
41
|
+
mas/lab/controller/routes/apps.py,sha256=ztdynzbxhbMfhxZ5sH3kD7kQt2M_I2j33AeXN0unFxU,7201
|
|
42
|
+
mas/lab/controller/routes/artifacts.py,sha256=k6eAfpVDWmDO7CH4jL09Ch6xVzd7bzD84JTM7PXbNu8,1251
|
|
43
|
+
mas/lab/controller/routes/benchmark.py,sha256=GTFQNizOrQt-I7iXt-EdinN-5k4YoqzrhxYevTGK4V8,11907
|
|
44
|
+
mas/lab/controller/routes/choices.py,sha256=UDnmaoL6VL4Pb8okys7KCiJgrcup71nDzJr01Jh2mIY,4913
|
|
45
|
+
mas/lab/controller/routes/datasets.py,sha256=SCPMV_6COC50h6TPvIIywmW-RlEUQoOjQMpfAaBf6b0,3627
|
|
46
|
+
mas/lab/controller/routes/design_patterns.py,sha256=4yT0lnDZnRGmjTCRG6CE0Mr7G-ksbtXLnxvOMS6cH5Y,4294
|
|
47
|
+
mas/lab/controller/routes/eval_output.py,sha256=y_URJXvkpguw06jJ_vFpsQliLuto7I6QKZtMXsDnv6U,1123
|
|
48
|
+
mas/lab/controller/routes/experiments.py,sha256=1YnHRUOeiZPwhYyEY-mgWLI7xq18hp_9dy4gVCR7awg,7691
|
|
49
|
+
mas/lab/controller/routes/health.py,sha256=bKqwVRRTLb1faF935y5B3b0FiEc3r1ThCprcDSf1ZyM,5988
|
|
50
|
+
mas/lab/controller/routes/items.py,sha256=LbXs6OtvZ9VtS1wWhanCMLuqJLhEpc4yRzqMDTczm0A,5997
|
|
51
|
+
mas/lab/controller/routes/jobs.py,sha256=AXD8fzRYHdDmRs5-Kzn2iLTirbAg8R2DGYkuDjFoh1Y,3895
|
|
52
|
+
mas/lab/controller/routes/libraries.py,sha256=drV_YR_OWvJQUoD0Lim97VAHSqjjQoqYJdRCxNph8Wg,5611
|
|
53
|
+
mas/lab/controller/routes/overlays.py,sha256=7zK7AahxJ0tDCc54HdvJKn5LelHIIBGlW8_WY5mDerE,6647
|
|
54
|
+
mas/lab/controller/routes/pipelines.py,sha256=-7eFcr6UqqRLLlgtH-b_0qBdgrNo4YIyI7N-c565i54,5699
|
|
55
|
+
mas/lab/controller/routes/registry.py,sha256=OW2Mm86G5utmWaSadugPcZcm_30m5-gaB-UDlhEfC9s,1307
|
|
56
|
+
mas/lab/controller/routes/run.py,sha256=noofHo7CHyMOS9XDvqfmpIb3xTYGxXJhODP5zqAUPS8,7507
|
|
57
|
+
mas/lab/controller/routes/runs.py,sha256=FspLVdpu-oaPi1fH1OpIz3cyEQS6kmiNUwcrzOZ8aIA,13913
|
|
58
|
+
mas/lab/controller/routes/scopes.py,sha256=eBVMFlo_f1lVJO3-GRLfBtPYWLYwXsuoJY5maanUzVU,5021
|
|
59
|
+
mas/lab/controller/routes/uploads.py,sha256=hdAXuwY8qyVKbMVGGLA69M3c2KFNr4SQTaOiz6FabpA,808
|
|
60
|
+
mas/lab/controller/routes/validate.py,sha256=TfFy513V0UVyZLKJgkGcA2B8oYnq-kdQQfIRanHFvMo,1452
|
|
61
|
+
mas/lab/controller/schemas/pipeline-step-types-post.json,sha256=vtvYp-sYSVynbqDsd9bys_K9TqZcpzDAx2E93A0Ez0w,24320
|
|
62
|
+
mas/lab/controller/schemas/pipeline-step-types-pre.json,sha256=yv5G2i37nMNrF34W00Ld-qlJGROnlgnEwOiyQAiyB-8,1446
|
|
63
|
+
mas/lab/controller/ui_stream/__init__.py,sha256=YESQ5Qt3S9G0gsCW7YwSgl_Guw0DDAC4gK7Y9xbtbYI,392
|
|
64
|
+
mas/lab/controller/ui_stream/app.py,sha256=WBpjGHFD3FCbWkogl8DqxhukyucA-QPVqXEcCjTg9LM,77850
|
|
65
|
+
mas/lab/controller/ui_stream/artefact_bind.py,sha256=9-6ciWsSZvTuGdWHgN54g6wZdnM_kpJ1CymdTrLcSoI,10548
|
|
66
|
+
mas/lab/controller/ui_stream/auth.py,sha256=29haISdJ521_KQX3gRx1TdNW59fVCtGCgC-B_djApvU,4234
|
|
67
|
+
mas/lab/controller/ui_stream/bus.py,sha256=U2GG3gIdWBGZMDh_9HBDQZu2sHieBVGSNSo0Mjmtotw,5470
|
|
68
|
+
mas/lab/controller/ui_stream/export.py,sha256=_2MsMzCkz-4yDAkS_yg1geiODS-hGJdvuZuqqQVW3pQ,14128
|
|
69
|
+
mas/lab/controller/ui_stream/governance.py,sha256=OEM8Qn7PdcVpFgA8aOoi2qk_8RHM4Zwg0QLyiTLjNB4,5638
|
|
70
|
+
mas/lab/controller/ui_stream/ingest.py,sha256=0PEmmcjWpN3594gdKW4qLP9gjvDPZVDFOusaK2PFu3c,11762
|
|
71
|
+
mas/lab/controller/ui_stream/inspect.py,sha256=z_tyJKW4DEbIMjao9fFxAHP7Ur2-xdkxSxtLW315ahA,1794
|
|
72
|
+
mas/lab/controller/ui_stream/layouts.py,sha256=2iTV6b4vzXd0_NvSdIWaAxb0qvE5KwSv2kfLU4jcnrQ,6171
|
|
73
|
+
mas/lab/controller/ui_stream/metrics.py,sha256=iPN5wF_tl5cAgeHdE4SwjNnq_P4lglU_usp-UOCMf18,9697
|
|
74
|
+
mas/lab/controller/ui_stream/plot_folds.py,sha256=Mk8ROe8VWFwGGZpFyhMulp8QRmqL2mQIYETp-dkJ0bw,21363
|
|
75
|
+
mas/lab/controller/ui_stream/plugin_registry.py,sha256=QFont6XVqB-IiEOUctO-uGx69cVUzdY-G_7EegBNVm0,4082
|
|
76
|
+
mas/lab/controller/ui_stream/refs.py,sha256=8y8mCOokJqNFMCOzCLwu_6FWOab7L8B_eJ5vTfqPwOs,7722
|
|
77
|
+
mas/lab/controller/ui_stream/replay.py,sha256=pSIH5TanEFbY4g_88Rzat2gwKFIXKCjSjNvdl93A4Fc,8116
|
|
78
|
+
mas/lab/controller/ui_stream/replay_cli.py,sha256=yHxy9UXXfEKawNxRa7Fq7sP8bZBuGoIDRYHDcZ83bSg,25753
|
|
79
|
+
mas/lab/controller/ui_stream/run_index.py,sha256=Qfklq4CYFrGBiRpg1yzwlkF-omL1vo-OpzImChGlZRE,11736
|
|
80
|
+
mas/lab/controller/ui_stream/spec_load.py,sha256=lBLGEKU9aMzgh8_s4VSkHWin3riya-P6SuziCpLWjsU,1814
|
|
81
|
+
mas/lab/controller/ui_stream/spec_snapshot.py,sha256=ZBfPkzJWigoF6PtYjFCI1i1BORnoYCgTQ8bJblagCAU,11037
|
|
82
|
+
mas/lab/controller/ui_stream/stream.py,sha256=1DYCA7qVNxacXSeDjQkj5VAya_ymDUSuxo07nrIwa1M,3152
|
|
83
|
+
mas/lab/controller/ui_stream/streams.py,sha256=riyKrKWVxvYFxSa5mjdgYumkIwFW-ahWPAa4iZ5Ny1Q,1573
|
|
84
|
+
mas/lab/controller/ui_stream/timeline.py,sha256=RyUIOh_EdbB3CBWQn5B6Kr9c1dMC6FVdvsWP_Z6GNdw,5776
|
|
85
|
+
mas/lab/controller/ui_stream/ui_trace.py,sha256=8T8WN6-W1eyvSQuWk3vRsQ0v3fhOLPEhFCXzR5Tl_X8,9439
|
|
86
|
+
mas_lab_component_controller-0.2.0.dist-info/METADATA,sha256=jOTMfrEiVfOjtyYKPgRqRlmE8gihMfoUxhbM1nycaeU,718
|
|
87
|
+
mas_lab_component_controller-0.2.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
88
|
+
mas_lab_component_controller-0.2.0.dist-info/entry_points.txt,sha256=ROEyM3R_CDwZy8STHFNl4on8qShxpY8rkkW2ZfbmGsw,84
|
|
89
|
+
mas_lab_component_controller-0.2.0.dist-info/RECORD,,
|