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.
Files changed (44) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +9 -8
  3. package/livepilot/.Codex-plugin/plugin.json +2 -2
  4. package/livepilot/.claude-plugin/plugin.json +2 -2
  5. package/livepilot/agents/livepilot-producer/AGENT.md +2 -2
  6. package/livepilot/commands/beat.md +3 -3
  7. package/livepilot/commands/evaluate.md +3 -1
  8. package/livepilot/commands/mix.md +2 -2
  9. package/livepilot/commands/sounddesign.md +2 -2
  10. package/livepilot/skills/livepilot-core/SKILL.md +10 -10
  11. package/livepilot/skills/livepilot-core/references/device-knowledge/effects-space.md +2 -1
  12. package/livepilot/skills/livepilot-core/references/overview.md +5 -3
  13. package/livepilot/skills/livepilot-core/references/sound-design.md +5 -2
  14. package/livepilot/skills/livepilot-creative-director/SKILL.md +43 -0
  15. package/livepilot/skills/livepilot-creative-director/references/move-family-diversity-rule.md +1 -1
  16. package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +15 -9
  17. package/livepilot/skills/livepilot-mix-engine/SKILL.md +9 -1
  18. package/livepilot/skills/livepilot-mixing/SKILL.md +12 -5
  19. package/livepilot/skills/livepilot-release/SKILL.md +9 -7
  20. package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +25 -3
  21. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  22. package/m4l_device/livepilot_bridge.js +1 -1
  23. package/mcp_server/__init__.py +1 -1
  24. package/mcp_server/composer/full/brief_builder.py +9 -0
  25. package/mcp_server/evaluation/feature_extractors.py +152 -8
  26. package/mcp_server/mix_engine/state_builder.py +19 -2
  27. package/mcp_server/mix_engine/tools.py +22 -0
  28. package/mcp_server/runtime/capability_probe.py +1 -1
  29. package/mcp_server/runtime/capability_state.py +66 -9
  30. package/mcp_server/runtime/live_version.py +18 -0
  31. package/mcp_server/runtime/session_kernel.py +7 -0
  32. package/mcp_server/runtime/tools.py +324 -22
  33. package/mcp_server/sound_design/tools.py +33 -0
  34. package/mcp_server/tools/_agent_os_engine/evaluation.py +7 -44
  35. package/mcp_server/tools/_agent_os_engine/models.py +2 -1
  36. package/mcp_server/tools/_conductor.py +5 -2
  37. package/mcp_server/tools/_evaluation_contracts.py +1 -1
  38. package/mcp_server/tools/_snapshot_normalizer.py +32 -3
  39. package/mcp_server/tools/analyzer.py +38 -1
  40. package/package.json +2 -2
  41. package/remote_script/LivePilot/__init__.py +1 -1
  42. package/remote_script/LivePilot/version_detect.py +3 -0
  43. package/requirements.txt +3 -3
  44. 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
- if not bands:
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
- return {
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.26.1",
3
+ "version": "1.27.0",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "Agentic production system for Ableton Live 12 — 465 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",
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.26.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.1,<3.4.0 # pinned upper bound — _get_all_tools() accesses private internals
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.13.1
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.80.0
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": "465-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",
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.26.1",
9
+ "version": "1.27.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "livepilot",
14
- "version": "1.26.1",
14
+ "version": "1.27.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }