livepilot 1.23.3 → 1.23.5
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.
- package/CHANGELOG.md +110 -0
- package/README.md +106 -8
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/atlas/cross_pack_chain.py +658 -0
- package/mcp_server/atlas/demo_story.py +700 -0
- package/mcp_server/atlas/extract_chain.py +786 -0
- package/mcp_server/atlas/macro_fingerprint.py +554 -0
- package/mcp_server/atlas/overlays.py +95 -3
- package/mcp_server/atlas/pack_aware_compose.py +1255 -0
- package/mcp_server/atlas/preset_resolver.py +238 -0
- package/mcp_server/atlas/tools.py +1001 -31
- package/mcp_server/atlas/transplant.py +1177 -0
- package/mcp_server/mix_engine/state_builder.py +44 -1
- package/mcp_server/runtime/capability_state.py +34 -3
- package/mcp_server/server.py +45 -24
- package/mcp_server/tools/agent_os.py +33 -9
- package/mcp_server/tools/analyzer.py +38 -7
- package/mcp_server/tools/browser.py +20 -1
- package/mcp_server/tools/devices.py +78 -11
- package/mcp_server/tools/perception.py +5 -1
- package/mcp_server/tools/tracks.py +39 -2
- package/mcp_server/user_corpus/__init__.py +48 -0
- package/mcp_server/user_corpus/manifest.py +142 -0
- package/mcp_server/user_corpus/plugin_engine/__init__.py +39 -0
- package/mcp_server/user_corpus/plugin_engine/detector.py +579 -0
- package/mcp_server/user_corpus/plugin_engine/manual.py +347 -0
- package/mcp_server/user_corpus/plugin_engine/research.py +247 -0
- package/mcp_server/user_corpus/runner.py +261 -0
- package/mcp_server/user_corpus/scanner.py +115 -0
- package/mcp_server/user_corpus/scanners/__init__.py +18 -0
- package/mcp_server/user_corpus/scanners/adg.py +79 -0
- package/mcp_server/user_corpus/scanners/als.py +144 -0
- package/mcp_server/user_corpus/scanners/amxd.py +374 -0
- package/mcp_server/user_corpus/scanners/plugin_preset.py +202 -0
- package/mcp_server/user_corpus/tools.py +904 -0
- package/mcp_server/user_corpus/wizard.py +224 -0
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/remote_script/LivePilot/browser.py +7 -2
- package/remote_script/LivePilot/server.py +38 -22
- package/remote_script/LivePilot/transport.py +15 -5
- package/requirements.txt +3 -3
- package/server.json +2 -2
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Resolve a demo track's device → matching preset sidecar.
|
|
2
|
+
|
|
3
|
+
Demo sidecars record device class + user_name but NOT macro names. Preset
|
|
4
|
+
sidecars carry the producer-assigned macro names. This module bridges the
|
|
5
|
+
two so plan emitters can output executable set_device_parameter steps with
|
|
6
|
+
real names (e.g. "Rift Rate") instead of generic "Macro N" labels, AND can
|
|
7
|
+
suggest a search_browser query for load_browser_item URI resolution.
|
|
8
|
+
|
|
9
|
+
Demo macro shape (no name):
|
|
10
|
+
[{"index": 0, "value": "1"}, ...]
|
|
11
|
+
Preset macro shape (with producer-assigned name):
|
|
12
|
+
[{"index": 0, "value": "1", "name": "Rift Rate"}, ...]
|
|
13
|
+
|
|
14
|
+
Live's browser URIs are FileId-keyed and require a runtime browser query —
|
|
15
|
+
there's no static URI we can derive from the sidecar alone, so this module
|
|
16
|
+
returns a search_browser HINT that the agent uses to resolve to a concrete
|
|
17
|
+
URI before calling load_browser_item.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
from functools import lru_cache
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
PRESET_PARSES_ROOT = (
|
|
28
|
+
Path.home() / ".livepilot" / "atlas-overlays" / "packs" / "_preset_parses"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@lru_cache(maxsize=64)
|
|
33
|
+
def _load_pack_index(pack_slug: str) -> tuple[tuple[str, str, str], ...]:
|
|
34
|
+
"""Return tuple of (preset_name, rack_class, sidecar_path) for the pack.
|
|
35
|
+
|
|
36
|
+
Cached. Returns empty tuple if pack dir doesn't exist.
|
|
37
|
+
"""
|
|
38
|
+
pack_dir = PRESET_PARSES_ROOT / pack_slug
|
|
39
|
+
if not pack_dir.is_dir():
|
|
40
|
+
# Try hyphen/underscore swap as a safety net
|
|
41
|
+
alt = PRESET_PARSES_ROOT / pack_slug.replace("_", "-")
|
|
42
|
+
if alt.is_dir():
|
|
43
|
+
pack_dir = alt
|
|
44
|
+
else:
|
|
45
|
+
return ()
|
|
46
|
+
entries: list[tuple[str, str, str]] = []
|
|
47
|
+
for sidecar in pack_dir.glob("*.json"):
|
|
48
|
+
try:
|
|
49
|
+
data = json.loads(sidecar.read_text(encoding="utf-8"))
|
|
50
|
+
except (OSError, json.JSONDecodeError):
|
|
51
|
+
continue
|
|
52
|
+
name = (data.get("name") or "").strip()
|
|
53
|
+
rack_class = (data.get("rack_class") or "").strip()
|
|
54
|
+
if name:
|
|
55
|
+
entries.append((name, rack_class, str(sidecar)))
|
|
56
|
+
return tuple(entries)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def resolve_preset_for_device(
|
|
60
|
+
pack_slug: str,
|
|
61
|
+
device_class: str,
|
|
62
|
+
device_user_name: str,
|
|
63
|
+
) -> dict:
|
|
64
|
+
"""Find the matching preset sidecar for a demo track's device.
|
|
65
|
+
|
|
66
|
+
Match priority:
|
|
67
|
+
1. Exact name match (case-insensitive) within the pack
|
|
68
|
+
2. Exact name match with matching rack_class
|
|
69
|
+
3. Substring match (preset_name in user_name OR vice versa)
|
|
70
|
+
4. None found → empty result
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
pack_slug: pack identifier, e.g. "drone-lab"
|
|
74
|
+
device_class: Live device class, e.g. "InstrumentGroupDevice"
|
|
75
|
+
(used as a tiebreaker; pass empty string to skip)
|
|
76
|
+
device_user_name: rack's user-visible name, e.g. "Pioneer Drone"
|
|
77
|
+
|
|
78
|
+
Returns dict:
|
|
79
|
+
found: bool
|
|
80
|
+
match_type: "exact" | "exact_with_class" | "partial" | "none"
|
|
81
|
+
sidecar_path: str | None
|
|
82
|
+
preset_name: str | None
|
|
83
|
+
macro_names: dict[int, str] # {macro_index: producer-assigned name}
|
|
84
|
+
browser_search_hint: dict | None
|
|
85
|
+
{name_filter: str, suggested_path: str}
|
|
86
|
+
preset_file: str | None # original .adg path for logging
|
|
87
|
+
"""
|
|
88
|
+
if not device_user_name or not pack_slug:
|
|
89
|
+
return _empty_result()
|
|
90
|
+
|
|
91
|
+
target = device_user_name.strip().lower()
|
|
92
|
+
pack_index = _load_pack_index(pack_slug)
|
|
93
|
+
if not pack_index:
|
|
94
|
+
return _empty_result()
|
|
95
|
+
|
|
96
|
+
# Pass 1: exact name + rack_class match (strongest)
|
|
97
|
+
if device_class:
|
|
98
|
+
for name, rack_class, sidecar_path in pack_index:
|
|
99
|
+
if name.lower() == target and rack_class == device_class:
|
|
100
|
+
return _build_result(sidecar_path, name, "exact_with_class")
|
|
101
|
+
|
|
102
|
+
# Pass 2: exact name match (any class)
|
|
103
|
+
for name, _rc, sidecar_path in pack_index:
|
|
104
|
+
if name.lower() == target:
|
|
105
|
+
return _build_result(sidecar_path, name, "exact")
|
|
106
|
+
|
|
107
|
+
# Pass 3: substring fallback
|
|
108
|
+
for name, _rc, sidecar_path in pack_index:
|
|
109
|
+
nlow = name.lower()
|
|
110
|
+
if (nlow and (nlow in target or target in nlow)
|
|
111
|
+
and abs(len(nlow) - len(target)) < max(len(nlow), len(target))):
|
|
112
|
+
return _build_result(sidecar_path, name, "partial")
|
|
113
|
+
|
|
114
|
+
return _empty_result()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def lookup_macro_name(
|
|
118
|
+
pack_slug: str, device_user_name: str, macro_index: int
|
|
119
|
+
) -> Optional[str]:
|
|
120
|
+
"""Convenience: get a single macro name without re-resolving every time."""
|
|
121
|
+
res = resolve_preset_for_device(pack_slug, "", device_user_name)
|
|
122
|
+
return res["macro_names"].get(macro_index)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ─── internals ────────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _build_result(sidecar_path: str, preset_name: str, match_type: str) -> dict:
|
|
129
|
+
try:
|
|
130
|
+
data = json.loads(Path(sidecar_path).read_text(encoding="utf-8"))
|
|
131
|
+
except (OSError, json.JSONDecodeError):
|
|
132
|
+
return _empty_result()
|
|
133
|
+
|
|
134
|
+
macros = data.get("macros") or []
|
|
135
|
+
macro_names: dict[int, str] = {}
|
|
136
|
+
for m in macros:
|
|
137
|
+
try:
|
|
138
|
+
idx = int(m.get("index", -1))
|
|
139
|
+
name = (m.get("name") or "").strip()
|
|
140
|
+
if idx >= 0 and name:
|
|
141
|
+
macro_names[idx] = name
|
|
142
|
+
except (ValueError, TypeError):
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
preset_file = data.get("file") or ""
|
|
146
|
+
return {
|
|
147
|
+
"found": True,
|
|
148
|
+
"match_type": match_type,
|
|
149
|
+
"sidecar_path": sidecar_path,
|
|
150
|
+
"preset_name": preset_name,
|
|
151
|
+
"macro_names": macro_names,
|
|
152
|
+
"browser_search_hint": {
|
|
153
|
+
"name_filter": preset_name,
|
|
154
|
+
"suggested_path": _infer_browser_path(preset_file),
|
|
155
|
+
},
|
|
156
|
+
"preset_file": preset_file,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _empty_result() -> dict:
|
|
161
|
+
return {
|
|
162
|
+
"found": False,
|
|
163
|
+
"match_type": "none",
|
|
164
|
+
"sidecar_path": None,
|
|
165
|
+
"preset_name": None,
|
|
166
|
+
"macro_names": {},
|
|
167
|
+
"browser_search_hint": None,
|
|
168
|
+
"preset_file": None,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _infer_browser_path(file_path: str) -> str:
|
|
173
|
+
"""Map a preset sidecar 'file' field to a search_browser path category.
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
"Drone Lab/Sounds/Synth Pad/Pioneer Drone.adg" → "sounds"
|
|
177
|
+
"Beat Tools/Drums/Kicks/Foo.adg" → "drums"
|
|
178
|
+
"Inspired by Nature/Instruments/Tree Tone.adg" → "instruments"
|
|
179
|
+
|
|
180
|
+
Defaults to "sounds" for unknown layouts (the broadest factory-pack
|
|
181
|
+
category for instrument racks).
|
|
182
|
+
"""
|
|
183
|
+
parts = [p.lower() for p in file_path.split("/") if p]
|
|
184
|
+
if len(parts) < 2:
|
|
185
|
+
return "sounds"
|
|
186
|
+
second = parts[1]
|
|
187
|
+
if "sound" in second or "synth" in second:
|
|
188
|
+
return "sounds"
|
|
189
|
+
if "drum" in second:
|
|
190
|
+
return "drums"
|
|
191
|
+
if "instrument" in second:
|
|
192
|
+
return "instruments"
|
|
193
|
+
if "audio effect" in second or "fx" in second or "effect" in second:
|
|
194
|
+
return "audio_effects"
|
|
195
|
+
if "midi" in second:
|
|
196
|
+
return "midi_effects"
|
|
197
|
+
return "sounds"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def emit_load_step(
|
|
201
|
+
pack_slug: str,
|
|
202
|
+
device_class: str,
|
|
203
|
+
device_user_name: str,
|
|
204
|
+
track_index: int,
|
|
205
|
+
) -> dict:
|
|
206
|
+
"""Build a load_browser_item plan step with embedded search-resolution hint.
|
|
207
|
+
|
|
208
|
+
Returns a dict matching the shape downstream callers (extract_chain,
|
|
209
|
+
pack_aware_compose) emit into their executable_steps lists. The agent
|
|
210
|
+
is expected to call search_browser(**browser_search_hint) first, then
|
|
211
|
+
load_browser_item(track_index=track_index, uri=<resolved_uri>).
|
|
212
|
+
"""
|
|
213
|
+
res = resolve_preset_for_device(pack_slug, device_class, device_user_name)
|
|
214
|
+
step: dict = {
|
|
215
|
+
"action": "load_browser_item",
|
|
216
|
+
"track_index": track_index,
|
|
217
|
+
"name": device_user_name,
|
|
218
|
+
"device_class": device_class,
|
|
219
|
+
"comment": (
|
|
220
|
+
"Resolve URI via search_browser before calling load_browser_item. "
|
|
221
|
+
"The browser_search_hint provides the recommended path + name_filter."
|
|
222
|
+
),
|
|
223
|
+
}
|
|
224
|
+
if res["found"]:
|
|
225
|
+
step["browser_search_hint"] = res["browser_search_hint"]
|
|
226
|
+
step["preset_name"] = res["preset_name"]
|
|
227
|
+
step["preset_file"] = res["preset_file"]
|
|
228
|
+
step["match_type"] = res["match_type"]
|
|
229
|
+
else:
|
|
230
|
+
step["browser_search_hint"] = {
|
|
231
|
+
"name_filter": device_user_name,
|
|
232
|
+
"suggested_path": "sounds",
|
|
233
|
+
}
|
|
234
|
+
step["match_type"] = "none"
|
|
235
|
+
step["comment"] += (
|
|
236
|
+
f" (no preset sidecar found in pack '{pack_slug}'; using user_name as fallback)"
|
|
237
|
+
)
|
|
238
|
+
return step
|