livepilot 1.26.1 → 1.27.0
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 +61 -0
- package/README.md +9 -8
- package/livepilot/.Codex-plugin/plugin.json +2 -2
- package/livepilot/.claude-plugin/plugin.json +2 -2
- package/livepilot/agents/livepilot-producer/AGENT.md +2 -2
- package/livepilot/commands/beat.md +3 -3
- package/livepilot/commands/evaluate.md +3 -1
- package/livepilot/commands/mix.md +2 -2
- package/livepilot/commands/sounddesign.md +2 -2
- package/livepilot/skills/livepilot-core/SKILL.md +10 -10
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-space.md +2 -1
- package/livepilot/skills/livepilot-core/references/overview.md +5 -3
- package/livepilot/skills/livepilot-core/references/sound-design.md +5 -2
- package/livepilot/skills/livepilot-creative-director/SKILL.md +43 -0
- package/livepilot/skills/livepilot-creative-director/references/move-family-diversity-rule.md +1 -1
- package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +15 -9
- package/livepilot/skills/livepilot-mix-engine/SKILL.md +9 -1
- package/livepilot/skills/livepilot-mixing/SKILL.md +12 -5
- package/livepilot/skills/livepilot-release/SKILL.md +9 -7
- package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +25 -3
- 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/composer/full/brief_builder.py +9 -0
- package/mcp_server/evaluation/feature_extractors.py +152 -8
- package/mcp_server/mix_engine/state_builder.py +19 -2
- package/mcp_server/mix_engine/tools.py +22 -0
- package/mcp_server/runtime/capability_probe.py +1 -1
- package/mcp_server/runtime/capability_state.py +66 -9
- package/mcp_server/runtime/live_version.py +18 -0
- package/mcp_server/runtime/session_kernel.py +7 -0
- package/mcp_server/runtime/tools.py +324 -22
- package/mcp_server/sound_design/tools.py +33 -0
- package/mcp_server/tools/_agent_os_engine/evaluation.py +7 -44
- package/mcp_server/tools/_agent_os_engine/models.py +2 -1
- package/mcp_server/tools/_conductor.py +5 -2
- package/mcp_server/tools/_evaluation_contracts.py +1 -1
- package/mcp_server/tools/_snapshot_normalizer.py +32 -3
- package/mcp_server/tools/analyzer.py +38 -1
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/remote_script/LivePilot/version_detect.py +3 -0
- package/requirements.txt +3 -3
- package/server.json +3 -3
|
@@ -13,6 +13,18 @@ import time
|
|
|
13
13
|
from typing import Optional
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
_RICH_ANALYZER_KEYS = (
|
|
17
|
+
"spectral_shape",
|
|
18
|
+
"mel_bands",
|
|
19
|
+
"chroma",
|
|
20
|
+
"onset",
|
|
21
|
+
"onsets",
|
|
22
|
+
"novelty",
|
|
23
|
+
"loudness",
|
|
24
|
+
"sub_detail",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
16
28
|
def normalize_sonic_snapshot(
|
|
17
29
|
raw: Optional[dict],
|
|
18
30
|
source: str = "unknown",
|
|
@@ -20,6 +32,9 @@ def normalize_sonic_snapshot(
|
|
|
20
32
|
"""Normalize a raw analyzer/perception output into canonical snapshot form.
|
|
21
33
|
|
|
22
34
|
Accepts both {"bands": {...}} and {"spectrum": {...}} shapes.
|
|
35
|
+
Rich analyzer streams are preserved when present so evaluators can
|
|
36
|
+
reason from FluCoMa character descriptors instead of collapsing every
|
|
37
|
+
decision down to the 9-band spectrum.
|
|
23
38
|
Returns None if input is empty or None.
|
|
24
39
|
|
|
25
40
|
Canonical form:
|
|
@@ -28,6 +43,12 @@ def normalize_sonic_snapshot(
|
|
|
28
43
|
"rms": float or None,
|
|
29
44
|
"peak": float or None,
|
|
30
45
|
"detected_key": str or None,
|
|
46
|
+
"spectral_shape": dict or None,
|
|
47
|
+
"mel_bands": list or None,
|
|
48
|
+
"chroma": dict/list or None,
|
|
49
|
+
"onset": dict or None,
|
|
50
|
+
"novelty": dict or None,
|
|
51
|
+
"loudness": dict or None,
|
|
31
52
|
"source": str,
|
|
32
53
|
"normalized_at_ms": int,
|
|
33
54
|
}
|
|
@@ -35,11 +56,12 @@ def normalize_sonic_snapshot(
|
|
|
35
56
|
if not raw or not isinstance(raw, dict):
|
|
36
57
|
return None
|
|
37
58
|
|
|
38
|
-
bands = raw.get("spectrum") or raw.get("bands")
|
|
39
|
-
|
|
59
|
+
bands = raw.get("spectrum") or raw.get("bands") or {}
|
|
60
|
+
has_rich_analyzer_data = any(raw.get(k) is not None for k in _RICH_ANALYZER_KEYS)
|
|
61
|
+
if not bands and not has_rich_analyzer_data:
|
|
40
62
|
return None
|
|
41
63
|
|
|
42
|
-
|
|
64
|
+
normalized = {
|
|
43
65
|
"spectrum": bands,
|
|
44
66
|
"rms": raw.get("rms"),
|
|
45
67
|
"peak": raw.get("peak"),
|
|
@@ -47,3 +69,10 @@ def normalize_sonic_snapshot(
|
|
|
47
69
|
"source": source,
|
|
48
70
|
"normalized_at_ms": int(time.time() * 1000),
|
|
49
71
|
}
|
|
72
|
+
|
|
73
|
+
for key in _RICH_ANALYZER_KEYS:
|
|
74
|
+
if key in raw and raw.get(key) is not None:
|
|
75
|
+
out_key = "onset" if key == "onsets" else key
|
|
76
|
+
normalized[out_key] = raw.get(key)
|
|
77
|
+
|
|
78
|
+
return normalized
|
|
@@ -171,6 +171,18 @@ async def _try_native_replace_sample(
|
|
|
171
171
|
return resp
|
|
172
172
|
|
|
173
173
|
|
|
174
|
+
def _normalize_native_fallback_reason(skip_reason: Optional[str]) -> Optional[str]:
|
|
175
|
+
if not skip_reason:
|
|
176
|
+
return None
|
|
177
|
+
if skip_reason.startswith("gate_closed:"):
|
|
178
|
+
return "live_version_below_12_4"
|
|
179
|
+
return skip_reason
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _native_dispatch_was_attempted(skip_reason: Optional[str]) -> bool:
|
|
183
|
+
return bool(skip_reason) and not skip_reason.startswith("gate_closed:")
|
|
184
|
+
|
|
185
|
+
|
|
174
186
|
@mcp.tool()
|
|
175
187
|
async def reconnect_bridge(ctx: Context) -> dict:
|
|
176
188
|
"""Attempt to reconnect the M4L UDP bridge (port 9880).
|
|
@@ -627,6 +639,9 @@ async def replace_simpler_sample(
|
|
|
627
639
|
result = dict(native)
|
|
628
640
|
result.update(hygiene)
|
|
629
641
|
result["method"] = "native_12_4" # preserved in case hygiene ever adds its own key
|
|
642
|
+
result["native_attempted"] = True
|
|
643
|
+
result["bridge_attempted"] = False
|
|
644
|
+
result["fallback_reason"] = None
|
|
630
645
|
return result
|
|
631
646
|
|
|
632
647
|
# Pre-12.4 fallback: M4L bridge path (unchanged behavior).
|
|
@@ -636,11 +651,17 @@ async def replace_simpler_sample(
|
|
|
636
651
|
)
|
|
637
652
|
|
|
638
653
|
if "error" in result:
|
|
654
|
+
result["native_attempted"] = _native_dispatch_was_attempted(skip_reason)
|
|
655
|
+
result["bridge_attempted"] = True
|
|
656
|
+
result["fallback_reason"] = _normalize_native_fallback_reason(skip_reason)
|
|
639
657
|
return result
|
|
640
658
|
if not result.get("sample_loaded"):
|
|
641
659
|
return {
|
|
642
660
|
"error": "Sample may not have loaded. Ensure the Simpler already "
|
|
643
|
-
"has a sample loaded — replace_sample silently fails on empty Simplers."
|
|
661
|
+
"has a sample loaded — replace_sample silently fails on empty Simplers.",
|
|
662
|
+
"native_attempted": _native_dispatch_was_attempted(skip_reason),
|
|
663
|
+
"bridge_attempted": True,
|
|
664
|
+
"fallback_reason": _normalize_native_fallback_reason(skip_reason),
|
|
644
665
|
}
|
|
645
666
|
|
|
646
667
|
hygiene = await _simpler_post_load_hygiene(
|
|
@@ -652,6 +673,9 @@ async def replace_simpler_sample(
|
|
|
652
673
|
|
|
653
674
|
result.update(hygiene)
|
|
654
675
|
result["method"] = "bridge_m4l"
|
|
676
|
+
result["native_attempted"] = _native_dispatch_was_attempted(skip_reason)
|
|
677
|
+
result["bridge_attempted"] = True
|
|
678
|
+
result["fallback_reason"] = _normalize_native_fallback_reason(skip_reason)
|
|
655
679
|
if skip_reason:
|
|
656
680
|
result["native_skip_reason"] = skip_reason
|
|
657
681
|
return result
|
|
@@ -692,7 +716,10 @@ async def load_sample_to_simpler(
|
|
|
692
716
|
# Live 12.4+: create an empty Simpler via insert_device, then use the
|
|
693
717
|
# native replace_sample path. Skips the dummy-sample bootstrap entirely.
|
|
694
718
|
caps = _live_caps(ctx)
|
|
719
|
+
native_attempted = False
|
|
720
|
+
fallback_reason = "live_version_below_12_4"
|
|
695
721
|
if caps.has_replace_sample_native:
|
|
722
|
+
fallback_reason = "native_insert_device_unavailable"
|
|
696
723
|
try:
|
|
697
724
|
ins = ableton.send_command("insert_device", {
|
|
698
725
|
"track_index": track_index,
|
|
@@ -701,6 +728,7 @@ async def load_sample_to_simpler(
|
|
|
701
728
|
except Exception:
|
|
702
729
|
ins = None
|
|
703
730
|
if isinstance(ins, dict) and "error" not in ins:
|
|
731
|
+
native_attempted = True
|
|
704
732
|
actual_device_index = ins.get("device_index", device_index)
|
|
705
733
|
native = await _try_native_replace_sample(
|
|
706
734
|
ctx, track_index, actual_device_index, file_path
|
|
@@ -717,7 +745,13 @@ async def load_sample_to_simpler(
|
|
|
717
745
|
result["method"] = "native_12_4"
|
|
718
746
|
result["device_index"] = actual_device_index
|
|
719
747
|
result["track_index"] = track_index
|
|
748
|
+
result["native_attempted"] = True
|
|
749
|
+
result["bridge_attempted"] = False
|
|
750
|
+
result["fallback_reason"] = None
|
|
720
751
|
return result
|
|
752
|
+
fallback_reason = _normalize_native_fallback_reason(
|
|
753
|
+
ctx.lifespan_context.get("_native_replace_skip_reason")
|
|
754
|
+
) or "native_replace_failed"
|
|
721
755
|
# Fall through to the legacy bootstrap path below on any failure.
|
|
722
756
|
|
|
723
757
|
# Step 1: Load a sample from the browser to create Simpler with content
|
|
@@ -774,6 +808,9 @@ async def load_sample_to_simpler(
|
|
|
774
808
|
result["method"] = "bootstrap_and_replace"
|
|
775
809
|
result["device_index"] = actual_device_index # additive — for step-result binding
|
|
776
810
|
result["track_index"] = track_index
|
|
811
|
+
result["native_attempted"] = native_attempted
|
|
812
|
+
result["bridge_attempted"] = True
|
|
813
|
+
result["fallback_reason"] = fallback_reason
|
|
777
814
|
return result
|
|
778
815
|
|
|
779
816
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"mcpName": "io.github.dreamrec/livepilot",
|
|
5
|
-
"description": "Agentic production system for Ableton Live 12 —
|
|
5
|
+
"description": "Agentic production system for Ableton Live 12 — 467 tools, 56 domains, 44 semantic moves. Device atlas (5264 devices, 120 enriched, 7 indexes), Splice intelligence (gRPC + GraphQL describe-a-sound + preview + collections + presets), 9-band spectral perception auto-loaded via ensure_analyzer_on_master, Creative Director skill, technique memory, 12 creative intelligence engines",
|
|
6
6
|
"author": "Pilot Studio",
|
|
7
7
|
"license": "BSL-1.1",
|
|
8
8
|
"type": "commonjs",
|
|
@@ -5,7 +5,7 @@ Entry point for the ControlSurface. Ableton calls create_instance(c_instance)
|
|
|
5
5
|
when this script is selected in Preferences > Link, Tempo & MIDI.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "1.
|
|
8
|
+
__version__ = "1.27.0"
|
|
9
9
|
|
|
10
10
|
from _Framework.ControlSurface import ControlSurface
|
|
11
11
|
from . import router
|
|
@@ -25,6 +25,9 @@ FEATURES = {
|
|
|
25
25
|
"drum_chain_in_note": (12, 3, 0),
|
|
26
26
|
"stem_separation": (12, 3, 0),
|
|
27
27
|
"device_ab_compare": (12, 3, 0),
|
|
28
|
+
"link_audio": (12, 4, 0),
|
|
29
|
+
"stem_time_selection": (12, 4, 0),
|
|
30
|
+
"stem_merge_selected": (12, 4, 0),
|
|
28
31
|
"replace_sample_native": (12, 4, 0),
|
|
29
32
|
"groove_pool_api": (11, 0, 0),
|
|
30
33
|
"rack_variations_api": (11, 0, 0),
|
package/requirements.txt
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# LivePilot MCP Server dependencies
|
|
2
2
|
numpy>=2.4.6
|
|
3
|
-
fastmcp>=3.
|
|
3
|
+
fastmcp>=3.4.2,<3.5.0 # pinned upper bound — _get_all_tools() accesses private internals
|
|
4
4
|
midiutil>=1.2.1
|
|
5
5
|
pretty_midi>=0.2.11
|
|
6
6
|
# v1.8 Perception Layer (offline analysis)
|
|
7
7
|
pyloudnorm>=0.2.0
|
|
8
|
-
soundfile>=0.
|
|
8
|
+
soundfile>=0.14.0
|
|
9
9
|
scipy>=1.17.1
|
|
10
10
|
mutagen>=1.47.0
|
|
11
11
|
# v1.10.5 Splice online catalog integration — required, not optional.
|
|
12
12
|
# Without these, SpliceGRPCClient silently disables itself and search_samples
|
|
13
13
|
# falls back to the SQLite sounds.db which only returns locally downloaded
|
|
14
14
|
# samples (see docs/2026-04-14-bugs-discovered.md — P0-2).
|
|
15
|
-
grpcio>=1.
|
|
15
|
+
grpcio>=1.81.1
|
|
16
16
|
protobuf>=7.35.0
|
|
17
17
|
|
|
18
18
|
# Known benign warning during install:
|
package/server.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.dreamrec/livepilot",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "467-tool agentic MCP production system for Ableton Live 12 \u2014 56 domains, 44 semantic moves, device atlas (5264 devices), Splice intelligence (gRPC + GraphQL), 9-band spectral perception auto-loaded, Creative Director skill, technique memory, 12 creative engines",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "https://github.com/dreamrec/LivePilot",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.27.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "livepilot",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.27.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|