livepilot 1.9.23 → 1.10.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/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +119 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +144 -13
- package/bin/livepilot.js +87 -0
- package/installer/codex.js +147 -0
- package/livepilot/.Codex-plugin/plugin.json +2 -2
- package/livepilot/.claude-plugin/plugin.json +2 -2
- package/livepilot/skills/livepilot-core/SKILL.md +21 -4
- package/livepilot/skills/livepilot-core/references/device-knowledge/00-index.md +34 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/automation-as-music.md +204 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/chains-genre.md +173 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/creative-thinking.md +211 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-distortion.md +188 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-space.md +162 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-spectral.md +229 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/instruments-synths.md +243 -0
- package/livepilot/skills/livepilot-core/references/overview.md +13 -9
- package/livepilot/skills/livepilot-core/references/sample-manipulation.md +724 -0
- package/livepilot/skills/livepilot-core/references/sound-design-deep.md +140 -0
- package/livepilot/skills/livepilot-devices/SKILL.md +16 -2
- package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
- package/livepilot/skills/livepilot-release/SKILL.md +19 -5
- package/livepilot/skills/livepilot-sample-engine/SKILL.md +104 -0
- package/livepilot/skills/livepilot-sample-engine/references/sample-critics.md +87 -0
- package/livepilot/skills/livepilot-sample-engine/references/sample-philosophy.md +51 -0
- package/livepilot/skills/livepilot-sample-engine/references/sample-techniques.md +131 -0
- package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +45 -0
- package/livepilot/skills/livepilot-wonder/SKILL.md +15 -0
- package/livepilot.mcpb +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/manifest.json +2 -2
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/atlas/__init__.py +357 -0
- package/mcp_server/atlas/device_atlas.json +44067 -0
- package/mcp_server/atlas/enrichments/__init__.py +111 -0
- package/mcp_server/atlas/enrichments/audio_effects/auto_filter.yaml +162 -0
- package/mcp_server/atlas/enrichments/audio_effects/beat_repeat.yaml +183 -0
- package/mcp_server/atlas/enrichments/audio_effects/channel_eq.yaml +126 -0
- package/mcp_server/atlas/enrichments/audio_effects/chorus_ensemble.yaml +149 -0
- package/mcp_server/atlas/enrichments/audio_effects/color_limiter.yaml +109 -0
- package/mcp_server/atlas/enrichments/audio_effects/compressor.yaml +159 -0
- package/mcp_server/atlas/enrichments/audio_effects/convolution_reverb.yaml +143 -0
- package/mcp_server/atlas/enrichments/audio_effects/convolution_reverb_pro.yaml +178 -0
- package/mcp_server/atlas/enrichments/audio_effects/delay.yaml +151 -0
- package/mcp_server/atlas/enrichments/audio_effects/drum_buss.yaml +142 -0
- package/mcp_server/atlas/enrichments/audio_effects/dynamic_tube.yaml +147 -0
- package/mcp_server/atlas/enrichments/audio_effects/echo.yaml +167 -0
- package/mcp_server/atlas/enrichments/audio_effects/eq_eight.yaml +148 -0
- package/mcp_server/atlas/enrichments/audio_effects/eq_three.yaml +121 -0
- package/mcp_server/atlas/enrichments/audio_effects/erosion.yaml +103 -0
- package/mcp_server/atlas/enrichments/audio_effects/filter_delay.yaml +173 -0
- package/mcp_server/atlas/enrichments/audio_effects/gate.yaml +130 -0
- package/mcp_server/atlas/enrichments/audio_effects/gated_delay.yaml +133 -0
- package/mcp_server/atlas/enrichments/audio_effects/glue_compressor.yaml +142 -0
- package/mcp_server/atlas/enrichments/audio_effects/grain_delay.yaml +141 -0
- package/mcp_server/atlas/enrichments/audio_effects/hybrid_reverb.yaml +160 -0
- package/mcp_server/atlas/enrichments/audio_effects/limiter.yaml +97 -0
- package/mcp_server/atlas/enrichments/audio_effects/multiband_dynamics.yaml +174 -0
- package/mcp_server/atlas/enrichments/audio_effects/overdrive.yaml +119 -0
- package/mcp_server/atlas/enrichments/audio_effects/pedal.yaml +145 -0
- package/mcp_server/atlas/enrichments/audio_effects/phaser_flanger.yaml +161 -0
- package/mcp_server/atlas/enrichments/audio_effects/redux.yaml +114 -0
- package/mcp_server/atlas/enrichments/audio_effects/reverb.yaml +190 -0
- package/mcp_server/atlas/enrichments/audio_effects/roar.yaml +159 -0
- package/mcp_server/atlas/enrichments/audio_effects/saturator.yaml +146 -0
- package/mcp_server/atlas/enrichments/audio_effects/shifter.yaml +154 -0
- package/mcp_server/atlas/enrichments/audio_effects/spectral_resonator.yaml +141 -0
- package/mcp_server/atlas/enrichments/audio_effects/spectral_time.yaml +164 -0
- package/mcp_server/atlas/enrichments/audio_effects/vector_delay.yaml +140 -0
- package/mcp_server/atlas/enrichments/audio_effects/vinyl_distortion.yaml +141 -0
- package/mcp_server/atlas/enrichments/instruments/analog.yaml +222 -0
- package/mcp_server/atlas/enrichments/instruments/bass.yaml +202 -0
- package/mcp_server/atlas/enrichments/instruments/collision.yaml +150 -0
- package/mcp_server/atlas/enrichments/instruments/drift.yaml +167 -0
- package/mcp_server/atlas/enrichments/instruments/electric.yaml +137 -0
- package/mcp_server/atlas/enrichments/instruments/emit.yaml +163 -0
- package/mcp_server/atlas/enrichments/instruments/meld.yaml +164 -0
- package/mcp_server/atlas/enrichments/instruments/operator.yaml +197 -0
- package/mcp_server/atlas/enrichments/instruments/poli.yaml +192 -0
- package/mcp_server/atlas/enrichments/instruments/sampler.yaml +218 -0
- package/mcp_server/atlas/enrichments/instruments/simpler.yaml +217 -0
- package/mcp_server/atlas/enrichments/instruments/tension.yaml +156 -0
- package/mcp_server/atlas/enrichments/instruments/tree_tone.yaml +162 -0
- package/mcp_server/atlas/enrichments/instruments/vector_fm.yaml +165 -0
- package/mcp_server/atlas/enrichments/instruments/vector_grain.yaml +166 -0
- package/mcp_server/atlas/enrichments/instruments/wavetable.yaml +162 -0
- package/mcp_server/atlas/enrichments/midi_effects/arpeggiator.yaml +156 -0
- package/mcp_server/atlas/enrichments/midi_effects/bouncy_notes.yaml +93 -0
- package/mcp_server/atlas/enrichments/midi_effects/chord.yaml +147 -0
- package/mcp_server/atlas/enrichments/midi_effects/melodic_steps.yaml +97 -0
- package/mcp_server/atlas/enrichments/midi_effects/note_echo.yaml +108 -0
- package/mcp_server/atlas/enrichments/midi_effects/note_length.yaml +97 -0
- package/mcp_server/atlas/enrichments/midi_effects/pitch.yaml +76 -0
- package/mcp_server/atlas/enrichments/midi_effects/random.yaml +117 -0
- package/mcp_server/atlas/enrichments/midi_effects/rhythmic_steps.yaml +103 -0
- package/mcp_server/atlas/enrichments/midi_effects/scale.yaml +83 -0
- package/mcp_server/atlas/enrichments/midi_effects/step_arp.yaml +112 -0
- package/mcp_server/atlas/enrichments/midi_effects/velocity.yaml +119 -0
- package/mcp_server/atlas/enrichments/utility/amp.yaml +159 -0
- package/mcp_server/atlas/enrichments/utility/cabinet.yaml +109 -0
- package/mcp_server/atlas/enrichments/utility/corpus.yaml +150 -0
- package/mcp_server/atlas/enrichments/utility/resonators.yaml +131 -0
- package/mcp_server/atlas/enrichments/utility/spectrum.yaml +63 -0
- package/mcp_server/atlas/enrichments/utility/tuner.yaml +51 -0
- package/mcp_server/atlas/enrichments/utility/utility.yaml +136 -0
- package/mcp_server/atlas/enrichments/utility/vocoder.yaml +160 -0
- package/mcp_server/atlas/scanner.py +236 -0
- package/mcp_server/atlas/tools.py +224 -0
- package/mcp_server/composer/__init__.py +1 -0
- package/mcp_server/composer/engine.py +452 -0
- package/mcp_server/composer/layer_planner.py +427 -0
- package/mcp_server/composer/prompt_parser.py +329 -0
- package/mcp_server/composer/tools.py +201 -0
- package/mcp_server/connection.py +53 -8
- package/mcp_server/corpus/__init__.py +377 -0
- package/mcp_server/device_forge/__init__.py +1 -0
- package/mcp_server/device_forge/builder.py +377 -0
- package/mcp_server/device_forge/models.py +142 -0
- package/mcp_server/device_forge/templates.py +483 -0
- package/mcp_server/device_forge/tools.py +162 -0
- package/mcp_server/hook_hunter/analyzer.py +23 -0
- package/mcp_server/hook_hunter/models.py +1 -0
- package/mcp_server/hook_hunter/tools.py +4 -2
- package/mcp_server/m4l_bridge.py +1 -0
- package/mcp_server/memory/taste_graph.py +68 -1
- package/mcp_server/memory/tools.py +15 -4
- package/mcp_server/musical_intelligence/detectors.py +14 -1
- package/mcp_server/musical_intelligence/tools.py +11 -8
- package/mcp_server/persistence/__init__.py +1 -0
- package/mcp_server/persistence/base_store.py +82 -0
- package/mcp_server/persistence/project_store.py +106 -0
- package/mcp_server/persistence/taste_store.py +122 -0
- package/mcp_server/preview_studio/models.py +1 -0
- package/mcp_server/preview_studio/tools.py +56 -13
- package/mcp_server/runtime/capability.py +66 -0
- package/mcp_server/runtime/capability_probe.py +137 -0
- package/mcp_server/runtime/execution_router.py +143 -0
- package/mcp_server/runtime/live_version.py +102 -0
- package/mcp_server/runtime/remote_commands.py +87 -0
- package/mcp_server/runtime/tools.py +18 -4
- package/mcp_server/sample_engine/__init__.py +1 -0
- package/mcp_server/sample_engine/analyzer.py +216 -0
- package/mcp_server/sample_engine/critics.py +390 -0
- package/mcp_server/sample_engine/models.py +193 -0
- package/mcp_server/sample_engine/moves.py +127 -0
- package/mcp_server/sample_engine/planner.py +186 -0
- package/mcp_server/sample_engine/sources.py +540 -0
- package/mcp_server/sample_engine/techniques.py +908 -0
- package/mcp_server/sample_engine/tools.py +442 -0
- package/mcp_server/semantic_moves/__init__.py +3 -0
- package/mcp_server/semantic_moves/device_creation_moves.py +237 -0
- package/mcp_server/semantic_moves/mix_moves.py +41 -41
- package/mcp_server/semantic_moves/performance_moves.py +13 -13
- package/mcp_server/semantic_moves/sample_compilers.py +372 -0
- package/mcp_server/semantic_moves/sound_design_moves.py +15 -15
- package/mcp_server/semantic_moves/tools.py +18 -17
- package/mcp_server/semantic_moves/transition_moves.py +16 -16
- package/mcp_server/server.py +51 -0
- package/mcp_server/services/__init__.py +1 -0
- package/mcp_server/services/motif_service.py +67 -0
- package/mcp_server/session_continuity/tracker.py +29 -1
- package/mcp_server/song_brain/builder.py +28 -1
- package/mcp_server/song_brain/models.py +4 -0
- package/mcp_server/song_brain/tools.py +20 -2
- package/mcp_server/sound_design/critics.py +89 -1
- package/mcp_server/splice_client/__init__.py +1 -0
- package/mcp_server/splice_client/client.py +347 -0
- package/mcp_server/splice_client/models.py +96 -0
- package/mcp_server/splice_client/protos/__init__.py +1 -0
- package/mcp_server/splice_client/protos/app_pb2.py +319 -0
- package/mcp_server/splice_client/protos/app_pb2.pyi +1153 -0
- package/mcp_server/splice_client/protos/app_pb2_grpc.py +1946 -0
- package/mcp_server/tools/arrangement.py +69 -0
- package/mcp_server/tools/automation.py +15 -2
- package/mcp_server/tools/devices.py +117 -6
- package/mcp_server/tools/notes.py +37 -4
- package/mcp_server/wonder_mode/diagnosis.py +5 -0
- package/mcp_server/wonder_mode/engine.py +85 -1
- package/mcp_server/wonder_mode/tools.py +6 -1
- package/package.json +12 -2
- package/remote_script/LivePilot/__init__.py +8 -1
- package/remote_script/LivePilot/arrangement.py +114 -0
- package/remote_script/LivePilot/browser.py +56 -1
- package/remote_script/LivePilot/devices.py +236 -6
- package/remote_script/LivePilot/mixing.py +8 -3
- package/remote_script/LivePilot/server.py +5 -1
- package/remote_script/LivePilot/transport.py +3 -0
- package/remote_script/LivePilot/version_detect.py +78 -0
- package/scripts/sync_metadata.py +132 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Capability and degradation reporting for advanced tools.
|
|
2
|
+
|
|
3
|
+
Every advanced tool reports its operational state so callers know
|
|
4
|
+
what data was available, what was missing, and how much to trust
|
|
5
|
+
the result.
|
|
6
|
+
|
|
7
|
+
Levels:
|
|
8
|
+
full — all required data sources available
|
|
9
|
+
fallback — some data missing, result is degraded but useful
|
|
10
|
+
analytical_only — no live data, pure heuristic
|
|
11
|
+
unavailable — cannot operate at all
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class CapabilityReport:
|
|
21
|
+
"""Operational state of an advanced tool invocation."""
|
|
22
|
+
|
|
23
|
+
level: str = "full" # full, fallback, analytical_only, unavailable
|
|
24
|
+
confidence: float = 1.0
|
|
25
|
+
available_sources: list[str] = field(default_factory=list)
|
|
26
|
+
missing_sources: list[str] = field(default_factory=list)
|
|
27
|
+
fallback_used: str = ""
|
|
28
|
+
reason: str = ""
|
|
29
|
+
|
|
30
|
+
def to_dict(self) -> dict:
|
|
31
|
+
d = {"capability": self.level, "confidence": round(self.confidence, 2)}
|
|
32
|
+
if self.missing_sources:
|
|
33
|
+
d["missing"] = self.missing_sources
|
|
34
|
+
if self.fallback_used:
|
|
35
|
+
d["fallback"] = self.fallback_used
|
|
36
|
+
if self.reason:
|
|
37
|
+
d["reason"] = self.reason
|
|
38
|
+
return d
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_capability(
|
|
42
|
+
required: list[str],
|
|
43
|
+
available: dict[str, bool],
|
|
44
|
+
) -> CapabilityReport:
|
|
45
|
+
"""Build a capability report from required vs available data sources."""
|
|
46
|
+
missing = [r for r in required if not available.get(r, False)]
|
|
47
|
+
present = [r for r in required if available.get(r, False)]
|
|
48
|
+
|
|
49
|
+
if not missing:
|
|
50
|
+
return CapabilityReport(
|
|
51
|
+
level="full", confidence=1.0, available_sources=present,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if len(missing) == len(required):
|
|
55
|
+
return CapabilityReport(
|
|
56
|
+
level="analytical_only", confidence=0.2,
|
|
57
|
+
available_sources=[], missing_sources=missing,
|
|
58
|
+
reason="No required data sources available",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
ratio = len(present) / len(required)
|
|
62
|
+
return CapabilityReport(
|
|
63
|
+
level="fallback", confidence=round(ratio * 0.8, 2),
|
|
64
|
+
available_sources=present, missing_sources=missing,
|
|
65
|
+
fallback_used="degraded inference from partial data",
|
|
66
|
+
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Runtime capability probe — detects what's available at startup.
|
|
2
|
+
|
|
3
|
+
Reports capability tiers: Core Control, Analyzer-Enhanced,
|
|
4
|
+
Offline Analysis, Creative Intelligence, Persistent Memory.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def probe_capabilities(
|
|
15
|
+
ableton: Any = None,
|
|
16
|
+
ctx: Any = None,
|
|
17
|
+
) -> dict:
|
|
18
|
+
"""Probe runtime capabilities and return a structured report.
|
|
19
|
+
|
|
20
|
+
Can be called at startup or on demand via --doctor.
|
|
21
|
+
"""
|
|
22
|
+
report: dict[str, dict] = {}
|
|
23
|
+
|
|
24
|
+
# 1. Ableton reachability
|
|
25
|
+
ableton_ok = False
|
|
26
|
+
if ableton is not None:
|
|
27
|
+
try:
|
|
28
|
+
info = ableton.send_command("ping")
|
|
29
|
+
ableton_ok = info is not None
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
32
|
+
report["ableton"] = {
|
|
33
|
+
"status": "ok" if ableton_ok else "unavailable",
|
|
34
|
+
"detail": "TCP 9878 connection active" if ableton_ok else "Not connected",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# 1b. Live version capabilities
|
|
38
|
+
live_version_str = "12.0.0"
|
|
39
|
+
if ableton_ok:
|
|
40
|
+
try:
|
|
41
|
+
info = ableton.send_command("get_session_info")
|
|
42
|
+
live_version_str = info.get("live_version", "12.0.0")
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
from .live_version import LiveVersionCapabilities
|
|
46
|
+
version_caps = LiveVersionCapabilities.from_version_string(live_version_str)
|
|
47
|
+
report["live_version"] = {
|
|
48
|
+
"status": "ok",
|
|
49
|
+
"version": live_version_str,
|
|
50
|
+
"capability_tier": version_caps.capability_tier,
|
|
51
|
+
"features": version_caps.to_dict(),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# 2. Remote Script parity
|
|
55
|
+
from .remote_commands import REMOTE_COMMANDS
|
|
56
|
+
report["remote_script"] = {
|
|
57
|
+
"status": "ok",
|
|
58
|
+
"command_count": len(REMOTE_COMMANDS),
|
|
59
|
+
"detail": f"{len(REMOTE_COMMANDS)} registered commands",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# 3. M4L bridge
|
|
63
|
+
bridge_ok = False
|
|
64
|
+
if ctx is not None:
|
|
65
|
+
lifespan_context = getattr(ctx, "lifespan_context", {}) if hasattr(ctx, "lifespan_context") else {}
|
|
66
|
+
bridge = lifespan_context.get("m4l")
|
|
67
|
+
spectral = lifespan_context.get("spectral")
|
|
68
|
+
bridge_ok = bridge is not None and spectral is not None and getattr(spectral, "is_connected", False)
|
|
69
|
+
report["m4l_bridge"] = {
|
|
70
|
+
"status": "ok" if bridge_ok else "unavailable",
|
|
71
|
+
"detail": "UDP 9880 / OSC 9881 active" if bridge_ok else "Not connected — 30 analyzer tools unavailable",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# 4. Offline perception
|
|
75
|
+
numpy_ok = False
|
|
76
|
+
try:
|
|
77
|
+
import numpy # noqa: F401
|
|
78
|
+
numpy_ok = True
|
|
79
|
+
except ImportError:
|
|
80
|
+
pass
|
|
81
|
+
report["offline_perception"] = {
|
|
82
|
+
"status": "ok" if numpy_ok else "degraded",
|
|
83
|
+
"detail": "numpy available" if numpy_ok else "numpy not installed — offline analysis unavailable",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# 5. Persistence
|
|
87
|
+
livepilot_dir = Path.home() / ".livepilot"
|
|
88
|
+
persistence_ok = livepilot_dir.exists() and os.access(livepilot_dir, os.W_OK)
|
|
89
|
+
taste_exists = (livepilot_dir / "taste.json").exists()
|
|
90
|
+
techniques_exists = (livepilot_dir / "memory" / "techniques.json").exists()
|
|
91
|
+
report["persistence"] = {
|
|
92
|
+
"status": "ok" if persistence_ok else "unavailable",
|
|
93
|
+
"detail": f"~/.livepilot/ {'writable' if persistence_ok else 'not found'}",
|
|
94
|
+
"taste_store": taste_exists,
|
|
95
|
+
"technique_store": techniques_exists,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# 6. Capability tier — highest active tier
|
|
99
|
+
if ableton_ok and bridge_ok:
|
|
100
|
+
tier = "analyzer_enhanced"
|
|
101
|
+
elif ableton_ok:
|
|
102
|
+
tier = "core_control"
|
|
103
|
+
else:
|
|
104
|
+
tier = "creative_intelligence" # heuristic-only, no Ableton connection
|
|
105
|
+
|
|
106
|
+
report["tier"] = {
|
|
107
|
+
"active": tier,
|
|
108
|
+
"levels": {
|
|
109
|
+
"core_control": ableton_ok,
|
|
110
|
+
"analyzer_enhanced": ableton_ok and bridge_ok,
|
|
111
|
+
"offline_analysis": numpy_ok,
|
|
112
|
+
"creative_intelligence": True, # always available
|
|
113
|
+
"persistent_memory": persistence_ok,
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return report
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def format_doctor_report(report: dict) -> str:
|
|
121
|
+
"""Format capability report for --doctor output."""
|
|
122
|
+
lines = ["LivePilot Capability Report", "=" * 40]
|
|
123
|
+
|
|
124
|
+
icons = {"ok": " PASS", "unavailable": " FAIL", "degraded": " WARN"}
|
|
125
|
+
|
|
126
|
+
for area in ["ableton", "remote_script", "m4l_bridge", "offline_perception", "persistence"]:
|
|
127
|
+
info = report.get(area, {})
|
|
128
|
+
status = info.get("status", "unknown")
|
|
129
|
+
icon = icons.get(status, " ????")
|
|
130
|
+
detail = info.get("detail", "")
|
|
131
|
+
lines.append(f"{icon} {area}: {detail}")
|
|
132
|
+
|
|
133
|
+
tier = report.get("tier", {}).get("active", "unknown")
|
|
134
|
+
lines.append("")
|
|
135
|
+
lines.append(f"Active tier: {tier}")
|
|
136
|
+
|
|
137
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Unified execution router for compiled plan steps.
|
|
2
|
+
|
|
3
|
+
Classifies each step by backend (remote_command, mcp_tool, bridge_command)
|
|
4
|
+
and dispatches to the correct execution path. Replaces the pattern of
|
|
5
|
+
sending everything through ableton.send_command() blindly.
|
|
6
|
+
|
|
7
|
+
Step backends:
|
|
8
|
+
remote_command — valid Remote Script handler, goes through TCP
|
|
9
|
+
bridge_command — M4L bridge handler, goes through TCP (requires bridge)
|
|
10
|
+
mcp_tool — MCP-layer Python function, called directly
|
|
11
|
+
unknown — not a valid target anywhere
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Any, Optional
|
|
18
|
+
|
|
19
|
+
from .remote_commands import BRIDGE_COMMANDS, REMOTE_COMMANDS
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# MCP-only tools that exist as Python functions but NOT as TCP handlers.
|
|
23
|
+
# These must be called through direct import, not ableton.send_command().
|
|
24
|
+
MCP_TOOLS: frozenset[str] = frozenset({
|
|
25
|
+
"apply_automation_shape",
|
|
26
|
+
"apply_gesture_template",
|
|
27
|
+
"analyze_mix",
|
|
28
|
+
"get_master_spectrum",
|
|
29
|
+
"get_emotional_arc",
|
|
30
|
+
"capture_audio",
|
|
31
|
+
"get_motif_graph",
|
|
32
|
+
# Device Forge tools (MCP-only, no TCP handler)
|
|
33
|
+
"generate_m4l_effect",
|
|
34
|
+
"install_m4l_device",
|
|
35
|
+
"list_genexpr_templates",
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ExecutionResult:
|
|
41
|
+
"""Result of executing a single plan step."""
|
|
42
|
+
|
|
43
|
+
ok: bool = False
|
|
44
|
+
backend: str = ""
|
|
45
|
+
tool: str = ""
|
|
46
|
+
result: Any = None
|
|
47
|
+
error: str = ""
|
|
48
|
+
|
|
49
|
+
def to_dict(self) -> dict:
|
|
50
|
+
d = {"ok": self.ok, "backend": self.backend, "tool": self.tool}
|
|
51
|
+
if self.ok:
|
|
52
|
+
d["result"] = self.result
|
|
53
|
+
else:
|
|
54
|
+
d["error"] = self.error
|
|
55
|
+
return d
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def classify_step(tool: str) -> str:
|
|
59
|
+
"""Classify a step's execution backend."""
|
|
60
|
+
if tool in REMOTE_COMMANDS:
|
|
61
|
+
return "remote_command"
|
|
62
|
+
if tool in BRIDGE_COMMANDS:
|
|
63
|
+
return "bridge_command"
|
|
64
|
+
if tool in MCP_TOOLS:
|
|
65
|
+
return "mcp_tool"
|
|
66
|
+
return "unknown"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def execute_step(
|
|
70
|
+
tool: str,
|
|
71
|
+
params: dict,
|
|
72
|
+
ableton: Any = None,
|
|
73
|
+
ctx: Any = None,
|
|
74
|
+
declared_backend: str | None = None,
|
|
75
|
+
) -> ExecutionResult:
|
|
76
|
+
"""Execute a single plan step through the correct backend."""
|
|
77
|
+
backend = declared_backend if declared_backend in ("remote_command", "bridge_command", "mcp_tool") else classify_step(tool)
|
|
78
|
+
|
|
79
|
+
if backend in ("remote_command", "bridge_command"):
|
|
80
|
+
if ableton is None:
|
|
81
|
+
return ExecutionResult(
|
|
82
|
+
ok=False, backend=backend, tool=tool,
|
|
83
|
+
error="Ableton connection unavailable",
|
|
84
|
+
)
|
|
85
|
+
try:
|
|
86
|
+
result = ableton.send_command(tool, params)
|
|
87
|
+
return ExecutionResult(ok=True, backend=backend, tool=tool, result=result)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
return ExecutionResult(ok=False, backend=backend, tool=tool, error=str(e))
|
|
90
|
+
|
|
91
|
+
elif backend == "mcp_tool":
|
|
92
|
+
# MCP tools require direct Python dispatch.
|
|
93
|
+
# For now, return a clear error — full MCP dispatch is wired per-tool
|
|
94
|
+
# in the callers (apply_semantic_move, render_preview_variant).
|
|
95
|
+
return ExecutionResult(
|
|
96
|
+
ok=False, backend=backend, tool=tool,
|
|
97
|
+
error=f"MCP tool '{tool}' requires direct Python dispatch — "
|
|
98
|
+
f"not executable through TCP. Use the MCP layer directly.",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
else:
|
|
102
|
+
return ExecutionResult(
|
|
103
|
+
ok=False, backend="unknown", tool=tool,
|
|
104
|
+
error=f"Unknown tool '{tool}' — not a Remote Script command, "
|
|
105
|
+
f"bridge command, or registered MCP tool",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def execute_plan_steps(
|
|
110
|
+
steps: list[dict],
|
|
111
|
+
ableton: Any = None,
|
|
112
|
+
ctx: Any = None,
|
|
113
|
+
stop_on_failure: bool = True,
|
|
114
|
+
) -> list[ExecutionResult]:
|
|
115
|
+
"""Execute a list of plan steps, returning results for each.
|
|
116
|
+
|
|
117
|
+
Stops on first failure by default. Set stop_on_failure=False
|
|
118
|
+
to continue past errors (useful for best-effort execution).
|
|
119
|
+
"""
|
|
120
|
+
results: list[ExecutionResult] = []
|
|
121
|
+
|
|
122
|
+
for step in steps:
|
|
123
|
+
tool = step.get("tool") or step.get("command", "")
|
|
124
|
+
params = step.get("params") or step.get("args", {})
|
|
125
|
+
# Honor declared backend from step annotations (PR5) if present
|
|
126
|
+
declared_backend = step.get("backend")
|
|
127
|
+
|
|
128
|
+
if not tool:
|
|
129
|
+
results.append(ExecutionResult(
|
|
130
|
+
ok=False, backend="unknown", tool="",
|
|
131
|
+
error="Step has no tool/command field",
|
|
132
|
+
))
|
|
133
|
+
if stop_on_failure:
|
|
134
|
+
break
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
result = execute_step(tool, params, ableton=ableton, ctx=ctx, declared_backend=declared_backend)
|
|
138
|
+
results.append(result)
|
|
139
|
+
|
|
140
|
+
if not result.ok and stop_on_failure:
|
|
141
|
+
break
|
|
142
|
+
|
|
143
|
+
return results
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""MCP-side Live version capabilities model.
|
|
2
|
+
|
|
3
|
+
Pure data model — no I/O. Parses version info from get_session_info
|
|
4
|
+
responses and exposes feature flags for tool routing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class LiveVersionCapabilities:
|
|
14
|
+
"""Feature availability based on detected Live version."""
|
|
15
|
+
|
|
16
|
+
major: int = 12
|
|
17
|
+
minor: int = 0
|
|
18
|
+
patch: int = 0
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def from_version_string(cls, version_str: str) -> LiveVersionCapabilities:
|
|
22
|
+
"""Parse '12.3.6' into a capabilities instance."""
|
|
23
|
+
parts = version_str.split(".")
|
|
24
|
+
major = int(parts[0]) if len(parts) > 0 else 12
|
|
25
|
+
minor = int(parts[1]) if len(parts) > 1 else 0
|
|
26
|
+
patch = int(parts[2]) if len(parts) > 2 else 0
|
|
27
|
+
return cls(major=major, minor=minor, patch=patch)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_session_info(cls, session_info: dict) -> LiveVersionCapabilities:
|
|
31
|
+
"""Extract version from get_session_info response.
|
|
32
|
+
|
|
33
|
+
Looks for 'live_version' field. Falls back to 12.0.0 if absent
|
|
34
|
+
(pre-upgrade Remote Script).
|
|
35
|
+
"""
|
|
36
|
+
version_str = session_info.get("live_version", "12.0.0")
|
|
37
|
+
return cls.from_version_string(version_str)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def _version_tuple(self) -> tuple[int, int, int]:
|
|
41
|
+
return (self.major, self.minor, self.patch)
|
|
42
|
+
|
|
43
|
+
# ── Feature flags ──────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def has_native_arrangement_clips(self) -> bool:
|
|
47
|
+
"""Track.create_midi_clip(start, length) — 12.1.10+"""
|
|
48
|
+
return self._version_tuple >= (12, 1, 10)
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def has_display_value(self) -> bool:
|
|
52
|
+
"""DeviceParameter.display_value — 12.2+"""
|
|
53
|
+
return self._version_tuple >= (12, 2, 0)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def has_insert_device(self) -> bool:
|
|
57
|
+
"""Track.insert_device(name, index?) — 12.3+"""
|
|
58
|
+
return self._version_tuple >= (12, 3, 0)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def has_drum_rack_construction(self) -> bool:
|
|
62
|
+
"""insert_chain + DrumChain.in_note — 12.3+"""
|
|
63
|
+
return self._version_tuple >= (12, 3, 0)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def has_take_lanes(self) -> bool:
|
|
67
|
+
"""Take Lanes API — 12.2+"""
|
|
68
|
+
return self._version_tuple >= (12, 2, 0)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def has_stem_separation(self) -> bool:
|
|
72
|
+
"""Stem separation via MFL — 12.3+"""
|
|
73
|
+
return self._version_tuple >= (12, 3, 0)
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def has_replace_sample_native(self) -> bool:
|
|
77
|
+
"""SimplerDevice.replace_sample(path) — 12.4+"""
|
|
78
|
+
return self._version_tuple >= (12, 4, 0)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def capability_tier(self) -> str:
|
|
82
|
+
"""Human-readable tier: core | enhanced_arrangement | full_intelligence."""
|
|
83
|
+
if self._version_tuple >= (12, 3, 0):
|
|
84
|
+
return "full_intelligence"
|
|
85
|
+
elif self._version_tuple >= (12, 1, 10):
|
|
86
|
+
return "enhanced_arrangement"
|
|
87
|
+
else:
|
|
88
|
+
return "core"
|
|
89
|
+
|
|
90
|
+
def to_dict(self) -> dict:
|
|
91
|
+
"""Serialize for API responses and capability probes."""
|
|
92
|
+
return {
|
|
93
|
+
"version": f"{self.major}.{self.minor}.{self.patch}",
|
|
94
|
+
"capability_tier": self.capability_tier,
|
|
95
|
+
"native_arrangement_clips": self.has_native_arrangement_clips,
|
|
96
|
+
"display_value": self.has_display_value,
|
|
97
|
+
"insert_device": self.has_insert_device,
|
|
98
|
+
"drum_rack_construction": self.has_drum_rack_construction,
|
|
99
|
+
"take_lanes": self.has_take_lanes,
|
|
100
|
+
"stem_separation": self.has_stem_separation,
|
|
101
|
+
"replace_sample_native": self.has_replace_sample_native,
|
|
102
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Canonical set of all valid Remote Script commands.
|
|
2
|
+
|
|
3
|
+
Every command here has a @register handler in remote_script/LivePilot/.
|
|
4
|
+
This is the source of truth for what can be called via
|
|
5
|
+
ableton.send_command(). If a command is not in this set, sending it
|
|
6
|
+
through TCP will return NOT_FOUND from the Remote Script.
|
|
7
|
+
|
|
8
|
+
Maintained manually — the Remote Script uses Ableton's Python and
|
|
9
|
+
cannot be imported in CI. Update this when adding new handlers.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
REMOTE_COMMANDS: frozenset[str] = frozenset({
|
|
13
|
+
# transport (10)
|
|
14
|
+
"get_session_info", "set_tempo", "set_time_signature",
|
|
15
|
+
"start_playback", "stop_playback", "continue_playback",
|
|
16
|
+
"toggle_metronome", "set_session_loop", "undo", "redo",
|
|
17
|
+
# tracks (17)
|
|
18
|
+
"get_track_info", "create_midi_track", "create_audio_track",
|
|
19
|
+
"create_return_track", "delete_track", "duplicate_track",
|
|
20
|
+
"set_track_name", "set_track_color", "set_track_mute",
|
|
21
|
+
"set_track_solo", "set_track_arm", "stop_track_clips",
|
|
22
|
+
"set_group_fold", "set_track_input_monitoring",
|
|
23
|
+
"get_freeze_status", "freeze_track", "flatten_track",
|
|
24
|
+
# clips (11)
|
|
25
|
+
"get_clip_info", "create_clip", "delete_clip", "duplicate_clip",
|
|
26
|
+
"fire_clip", "stop_clip", "set_clip_name", "set_clip_color",
|
|
27
|
+
"set_clip_loop", "set_clip_launch", "set_clip_warp_mode",
|
|
28
|
+
# notes (8)
|
|
29
|
+
"add_notes", "get_notes", "remove_notes", "remove_notes_by_id",
|
|
30
|
+
"modify_notes", "duplicate_notes", "transpose_notes", "quantize_clip",
|
|
31
|
+
# mixing (11)
|
|
32
|
+
"set_track_volume", "set_track_pan", "set_track_send",
|
|
33
|
+
"get_return_tracks", "get_master_track", "set_master_volume",
|
|
34
|
+
"get_track_routing", "get_track_meters", "get_master_meters",
|
|
35
|
+
"get_mix_snapshot", "set_track_routing",
|
|
36
|
+
# scenes (12)
|
|
37
|
+
"get_scenes_info", "create_scene", "delete_scene", "duplicate_scene",
|
|
38
|
+
"fire_scene", "set_scene_name", "set_scene_color", "set_scene_tempo",
|
|
39
|
+
"get_scene_matrix", "fire_scene_clips", "stop_all_clips",
|
|
40
|
+
"get_playing_clips",
|
|
41
|
+
# devices (15)
|
|
42
|
+
"get_device_info", "get_device_parameters", "set_device_parameter",
|
|
43
|
+
"batch_set_parameters", "toggle_device", "delete_device",
|
|
44
|
+
"move_device", "load_device_by_uri", "find_and_load_device",
|
|
45
|
+
"set_simpler_playback_mode", "get_rack_chains", "set_chain_volume",
|
|
46
|
+
"insert_device", # 12.3+ native device insertion
|
|
47
|
+
"insert_rack_chain", # 12.3+ rack chain insertion
|
|
48
|
+
"set_drum_chain_note", # 12.3+ drum chain note assignment
|
|
49
|
+
# clip_automation (3)
|
|
50
|
+
"get_clip_automation", "set_clip_automation", "clear_clip_automation",
|
|
51
|
+
# browser (6)
|
|
52
|
+
"get_browser_tree", "get_browser_items", "search_browser",
|
|
53
|
+
"load_browser_item", "get_device_presets",
|
|
54
|
+
"scan_browser_deep", # Atlas deep scan — returns full category tree
|
|
55
|
+
# arrangement (21)
|
|
56
|
+
"get_arrangement_clips", "create_arrangement_clip",
|
|
57
|
+
"create_native_arrangement_clip",
|
|
58
|
+
"add_arrangement_notes", "get_arrangement_notes",
|
|
59
|
+
"remove_arrangement_notes", "remove_arrangement_notes_by_id",
|
|
60
|
+
"modify_arrangement_notes", "duplicate_arrangement_notes",
|
|
61
|
+
"set_arrangement_automation", "transpose_arrangement_notes",
|
|
62
|
+
"set_arrangement_clip_name", "jump_to_time",
|
|
63
|
+
"capture_midi", "start_recording", "stop_recording",
|
|
64
|
+
"get_cue_points", "jump_to_cue", "toggle_cue_point",
|
|
65
|
+
"back_to_arranger", "force_arrangement",
|
|
66
|
+
# diagnostics (1)
|
|
67
|
+
"get_session_diagnostics",
|
|
68
|
+
# ping (built-in)
|
|
69
|
+
"ping",
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
# M4L bridge commands — routed through TCP but handled by livepilot_bridge.js
|
|
73
|
+
# These require the M4L Analyzer device on the master track.
|
|
74
|
+
BRIDGE_COMMANDS: frozenset[str] = frozenset({
|
|
75
|
+
"get_params", "get_hidden_params", "get_auto_state", "walk_rack",
|
|
76
|
+
"get_chains_deep", "get_track_cpu", "get_selected", "get_key",
|
|
77
|
+
"get_clip_file_path", "replace_simpler_sample", "get_simpler_slices",
|
|
78
|
+
"crop_simpler", "reverse_simpler", "warp_simpler",
|
|
79
|
+
"get_warp_markers", "add_warp_marker", "move_warp_marker",
|
|
80
|
+
"remove_warp_marker", "capture_audio", "capture_stop",
|
|
81
|
+
"check_flucoma", "scrub_clip", "stop_scrub", "get_display_values",
|
|
82
|
+
"get_plugin_params", "map_plugin_param", "get_plugin_presets",
|
|
83
|
+
"load_sample_to_simpler",
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
# Combined: all valid send_command targets
|
|
87
|
+
ALL_VALID_COMMANDS: frozenset[str] = REMOTE_COMMANDS | BRIDGE_COMMANDS
|
|
@@ -94,14 +94,19 @@ def get_session_kernel(
|
|
|
94
94
|
|
|
95
95
|
# Core: session info + capability state
|
|
96
96
|
session_info = ableton.send_command("get_session_info")
|
|
97
|
+
session_ok = isinstance(session_info, dict) and "error" not in session_info
|
|
97
98
|
|
|
98
99
|
analyzer_ok = False
|
|
100
|
+
analyzer_fresh = False
|
|
99
101
|
if spectral is not None:
|
|
100
102
|
analyzer_ok = spectral.is_connected
|
|
103
|
+
if analyzer_ok:
|
|
104
|
+
analyzer_fresh = spectral.get("spectrum") is not None
|
|
101
105
|
|
|
102
106
|
state = build_capability_state(
|
|
103
|
-
session_ok=
|
|
107
|
+
session_ok=session_ok,
|
|
104
108
|
analyzer_ok=analyzer_ok,
|
|
109
|
+
analyzer_fresh=analyzer_fresh,
|
|
105
110
|
memory_ok=True,
|
|
106
111
|
)
|
|
107
112
|
|
|
@@ -112,10 +117,19 @@ def get_session_kernel(
|
|
|
112
117
|
session_mem = []
|
|
113
118
|
|
|
114
119
|
try:
|
|
115
|
-
from .action_ledger import
|
|
116
|
-
ledger =
|
|
120
|
+
from .action_ledger import SessionLedger
|
|
121
|
+
ledger = ctx.lifespan_context.get("action_ledger")
|
|
122
|
+
if ledger is None:
|
|
123
|
+
ledger = SessionLedger()
|
|
124
|
+
ctx.lifespan_context["action_ledger"] = ledger
|
|
117
125
|
if ledger:
|
|
118
|
-
|
|
126
|
+
recent = ledger.get_recent_moves(limit=10)
|
|
127
|
+
ledger_summary = {
|
|
128
|
+
"total_moves": len(ledger._entries),
|
|
129
|
+
"memory_candidate_count": len(ledger.get_memory_candidates()),
|
|
130
|
+
"last_move": ledger.get_last_move().to_dict() if ledger.get_last_move() else None,
|
|
131
|
+
"recent_moves": [entry.to_dict() for entry in recent],
|
|
132
|
+
}
|
|
119
133
|
except Exception:
|
|
120
134
|
pass
|
|
121
135
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Sample Engine — intelligence layer for sample discovery, analysis, and manipulation."""
|