livepilot 1.27.0 → 1.27.2

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 (66) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +26 -7
  3. package/bin/livepilot.js +86 -23
  4. package/installer/install.js +21 -12
  5. package/livepilot/.Codex-plugin/plugin.json +1 -1
  6. package/livepilot/.claude-plugin/plugin.json +1 -1
  7. package/livepilot/skills/livepilot-core/SKILL.md +8 -8
  8. package/livepilot/skills/livepilot-core/references/overview.md +2 -2
  9. package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
  10. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  11. package/m4l_device/livepilot_bridge.js +48 -11
  12. package/mcp_server/__init__.py +1 -1
  13. package/mcp_server/atlas/__init__.py +20 -1
  14. package/mcp_server/atlas/tools.py +83 -22
  15. package/mcp_server/composer/fast/brief_builder.py +6 -2
  16. package/mcp_server/composer/full/apply.py +9 -0
  17. package/mcp_server/composer/full/layer_planner.py +16 -6
  18. package/mcp_server/composer/tools.py +12 -6
  19. package/mcp_server/connection.py +2 -1
  20. package/mcp_server/creative_constraints/engine.py +46 -0
  21. package/mcp_server/experiment/engine.py +10 -3
  22. package/mcp_server/grader/client.py +30 -2
  23. package/mcp_server/grader/tools.py +8 -2
  24. package/mcp_server/hook_hunter/analyzer.py +15 -2
  25. package/mcp_server/m4l_bridge.py +145 -54
  26. package/mcp_server/memory/taste_graph.py +16 -0
  27. package/mcp_server/memory/technique_store.py +23 -3
  28. package/mcp_server/mix_engine/state_builder.py +4 -2
  29. package/mcp_server/musical_intelligence/detectors.py +11 -2
  30. package/mcp_server/musical_intelligence/tools.py +15 -2
  31. package/mcp_server/preview_studio/engine.py +30 -1
  32. package/mcp_server/project_brain/tools.py +56 -52
  33. package/mcp_server/reference_engine/tools.py +22 -2
  34. package/mcp_server/runtime/execution_router.py +6 -0
  35. package/mcp_server/runtime/live_version.py +27 -8
  36. package/mcp_server/runtime/mcp_dispatch.py +22 -0
  37. package/mcp_server/runtime/remote_commands.py +9 -4
  38. package/mcp_server/runtime/safety_kernel.py +11 -0
  39. package/mcp_server/sample_engine/critics.py +7 -3
  40. package/mcp_server/sample_engine/slice_workflow.py +3 -2
  41. package/mcp_server/sample_engine/tools.py +53 -21
  42. package/mcp_server/server.py +1 -1
  43. package/mcp_server/song_brain/builder.py +17 -1
  44. package/mcp_server/sound_design/tools.py +1 -1
  45. package/mcp_server/splice_client/http_bridge.py +43 -1
  46. package/mcp_server/splice_client/models.py +7 -3
  47. package/mcp_server/synthesis_brain/adapters/analog.py +13 -1
  48. package/mcp_server/synthesis_brain/adapters/operator.py +5 -2
  49. package/mcp_server/synthesis_brain/adapters/wavetable.py +4 -4
  50. package/mcp_server/tools/_composition_engine/models.py +6 -0
  51. package/mcp_server/tools/_composition_engine/sections.py +4 -0
  52. package/mcp_server/tools/clips.py +5 -4
  53. package/mcp_server/tools/composition.py +5 -1
  54. package/mcp_server/tools/midi_io.py +40 -1
  55. package/mcp_server/tools/transport.py +1 -1
  56. package/mcp_server/user_corpus/plugin_engine/detector.py +66 -11
  57. package/mcp_server/user_corpus/runner.py +7 -1
  58. package/mcp_server/user_corpus/scanners/amxd.py +24 -13
  59. package/mcp_server/user_corpus/tools.py +45 -9
  60. package/mcp_server/wonder_mode/tools.py +66 -27
  61. package/package.json +1 -1
  62. package/remote_script/LivePilot/__init__.py +21 -8
  63. package/remote_script/LivePilot/server.py +23 -3
  64. package/remote_script/LivePilot/tracks.py +11 -5
  65. package/requirements.txt +1 -1
  66. package/server.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,73 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.27.2 — 2026-06-23
4
+
5
+ Maintenance release: M4L bridge response-correlation + chunk-reassembly hardening, plan-routing fixes, a Python 3.11 floor with clearer install diagnostics, and a Remote Script Group-track crash guard. No change to the tool surface (467 tools / 56 domains). Re-frozen LivePilot_Analyzer.amxd (analyzer reports 1.27.2).
6
+
7
+ ### Fixed — M4L bridge
8
+ - Correlate responses by request id across batched reads (`get_params` / `get_hidden_params` / `get_auto_state`): the id is captured per command in the analyzer JS and echoed on both single-packet and chunked responses, so an interleaved or timed-out command can no longer resolve another command's future.
9
+ - Chunk reassembly buckets per request id, bounds-checks the chunk index, and requires every index present before reassembling — fixes a `KeyError` / silent-loss + full-timeout path on duplicate or out-of-range chunks.
10
+ - Non-blocking UDP socket so the miditool response path cannot stall the asyncio event loop; clear the capture future after completion.
11
+
12
+ ### Fixed — routing & Remote Script
13
+ - `compressor_set_sidechain` now classifies as an MCP tool (TCP Remote Script path) instead of being mis-routed to the M4L JS bridge; `get_master_rms` is now dispatchable in plans; removed the dead duplicate `get_simpler_file_path` bridge entry.
14
+ - `get_track_info` no longer crashes on Group/Return tracks (guards the LOM-fragile arm/input properties).
15
+ - `reload_handlers` reports per-module reload errors instead of silently swallowing them.
16
+ - Connection retry tears down the stale socket under the lock.
17
+
18
+ ### Changed — install & docs
19
+ - Raised the Python floor to 3.11 (numpy/scipy publish no wheels for 3.9/3.10) with a clear pre-flight message; pip failures surface captured output + a version hint, and the expected grpcio-tools resolver warning is pre-announced.
20
+ - `consult_ableton_knowledge` docstrings corrected; fast brief now notes the `mcp__Ableton_Knowledge__` prefix and that the knowledge MCP is optional; fixed an over-permissive tool-name test.
21
+
22
+
23
+ ## v1.27.1 — 2026-06-21
24
+
25
+ Maintenance release: 35 verified fixes from a deep multi-agent audit, recursive installed-plugin scanning, and Windows-CI hardening. No change to the tool surface (467 tools / 56 domains).
26
+
27
+ ### Restored — tools that were silently broken
28
+ - `augment_with_samples`, `get_composition_plan`, `propose_composer_branches` — crashed or returned nothing since the v1.24 refactor removed section templates; they now degrade to a single full-length section.
29
+ - `check_clip_key_consistency` — always returned `"unknown"` (removed FastMCP `.fn` accessor).
30
+ - `compare_phrase_renders` — returned an identical empty critique; now analyzes each render.
31
+
32
+ ### Fixed — correctness
33
+ - Reference engine: project spectrum and stereo width are now populated (gap analysis previously ran against an all-zero project).
34
+ - `infer_section_purposes`: drops are no longer mislabeled as tension.
35
+ - Grader: master/return/group tracks identified by their real field names (group containers were inflating track counts).
36
+ - Hook salience: the memorability boost no longer fires on every candidate.
37
+ - Mix: the `flat_dynamics` critic is now reachable (`over_compressed` is a 3–6 dB band).
38
+ - Wavetable adapter uses the real `Osc 1 Pos` parameter name.
39
+ - Simpler slice playback uses the correct base note (36+N); `vibe_fit` energy proxy normalized.
40
+ - Harmony and phrase-grid analysis read from the correct clip slot.
41
+ - `exclude_globs` now match files inside named directories.
42
+ - A single shared technique store so in-session saves are visible to recall/search.
43
+ - Read-only prefix matcher no longer misclassifies mutating tools as safe.
44
+ - Atlas id/name collisions no longer shadow entries; the overlay index is no longer rescanned per namespace.
45
+ - AMXD device-type map recognizes MIDI Tool devices (Live 12.1+).
46
+ - `apply_full_plan_v2` postflight no longer deletes a reused existing track.
47
+ - Fatigue level no longer diluted by low-severity issues; energy-arc no longer desyncs on skipped scenes; `create_preview_set` no longer silently overwrites an existing set.
48
+
49
+ ### Fixed — installed-plugin scanning (#44)
50
+ - The plugin scanner now recurses into vendor subfolders (e.g. `VST3/<Vendor>/Plugin.vst3`); nested plugins are found and vendor folders are no longer emitted as junk inventory records.
51
+
52
+ ### Fixed — safety and resources
53
+ - Splice `is_free` misclassification that could bypass credit/quota gating.
54
+ - Experiment rollback undoes only `remote_command` steps (no longer reverts unrelated edits).
55
+ - Timed-out write commands are dropped instead of re-executing later on the main thread.
56
+ - Order-tolerant M4L bridge chunk reassembly (no permanent response loss on UDP reordering).
57
+ - Installer install-path guard hardened; Splice bearer token gated to splice.com HTTPS hosts; non-numeric Live version strings tolerated instead of crashing the capability probe.
58
+
59
+ ### Performance
60
+ - Blocking sample I/O (SQLite, file decode, FFT, network) moved off the asyncio event loop.
61
+ - Capped previously-unbounded tool responses (`atlas_device_info`, `extension_atlas_search`, corpus synthesis briefs, piano-roll matrix, plugin `sdk_metadata`).
62
+ - Deduped redundant session round-trips (`enter_wonder_mode`, `build_project_brain`).
63
+
64
+ ### CI and tooling
65
+ - Fixed the Windows CI matrix (cp1252 `UnicodeEncodeError` in verifier scripts; POSIX-path test fixture).
66
+ - `build_mcpb.sh` enforces `.mcpbignore`; portable dev scripts.
67
+
68
+ ### Dependencies
69
+ - fastmcp, soundfile 0.14.0, grpcio 1.81.1, protobuf 7.35.1.
70
+
3
71
  ## v1.27.0 — 2026-06-16
4
72
 
5
73
  Probe-first Live 12.4 capability release.
package/README.md CHANGED
@@ -38,6 +38,20 @@
38
38
 
39
39
  ---
40
40
 
41
+ ## What's New in v1.27.1
42
+
43
+ A maintenance release that fixes 35 issues from a deep audit and restores tools that had silently broken:
44
+
45
+ - **Restored tools** — `augment_with_samples`, `get_composition_plan`, `propose_composer_branches`, `check_clip_key_consistency`, and `compare_phrase_renders` were crashing or returning empty results; they now work.
46
+ - **Recursive plugin scanning (#44)** — the User Corpus scanner now finds plugins nested in vendor subfolders (e.g. `VST3/<Vendor>/Plugin.vst3`) instead of only the top level.
47
+ - **Truer analysis** — reference-gap, hook-salience, dynamics, drop-detection, harmony-slot, and grader fixes that previously produced fabricated or empty results.
48
+ - **Safer & faster** — Splice credit-gating fixed, blocking sample I/O moved off the event loop, unbounded tool responses capped, and the M4L bridge no longer loses responses on UDP reordering.
49
+ - **v1.27.0** added two read-only Live 12.4 capability-probe tools (`probe_link_audio`, `probe_stem_workflow`).
50
+
51
+ Full details in the [CHANGELOG](CHANGELOG.md).
52
+
53
+ ---
54
+
41
55
  ## What LivePilot Does
42
56
 
43
57
  Most MCP servers are tool collections — they execute commands. LivePilot is an **agentic production system**. It has eight layers that work together:
@@ -473,7 +487,7 @@ Full track with song form: intro, verse, hook, breakdown, outro. Uses a two-phas
473
487
  - Native arrangement clips via `create_native_arrangement_clip` (one clip per section, looped to fill section length)
474
488
  - Zombie-track cleanup in postflight (removes tracks with no clips and no instrument device)
475
489
  - Drum-role pitch repair ported from fast mode
476
- - **Known gap (v1.25):** `KnowledgePack.atlas_candidates_per_role` is an empty stub the agent currently falls through to `search_browser` filename matching instead of consulting the indexed atlas. This is BUG-FULL-MODE-24 and is the headline feature of v1.25.
490
+ - **Atlas anchors:** role-level device knowledge is supplied through `KnowledgePack.atlas_anchors`, which consults the indexed atlas rather than filename matching. The older `atlas_candidates_per_role` field is a deprecated legacy stub, kept empty for back-compat.
477
491
  - Invoke with: *"Write a full [genre] track at [tempo] BPM"* or *"Build a full arrangement"*
478
492
 
479
493
  #### develop mode — `develop_apply`
@@ -492,7 +506,7 @@ All three modes share a `KnowledgePack` that provides structured creative contex
492
506
  - `event_lexicon` — 42 structural events across 7 categories (drum density, harmonic, texture, vocal, rhythm feel, tension, fx gesture)
493
507
  - `genre_context` — parses the 15-genre `genre-vocabularies.md` at load time
494
508
  - `artist_context` — parses the ~25-producer `artist-vocabularies.md` at load time
495
- - `atlas_candidates_per_role` — **stubbed in v1.25.0**, will be populated in v1.25
509
+ - `atlas_anchors` — indexed-atlas device anchors per role (replaced the legacy `atlas_candidates_per_role` stub)
496
510
 
497
511
  #### Core composer tools
498
512
 
@@ -550,7 +564,7 @@ The V2 intelligence layer. These tools analyze, diagnose, plan, evaluate, and le
550
564
 
551
565
  ### Easiest: Claude Desktop Extension (1 click)
552
566
 
553
- Download [`livepilot.mcpb`](https://github.com/dreamrec/LivePilot/releases/latest) and double-click it.
567
+ Download the latest `livepilot-<version>.mcpb` from the [Releases page](https://github.com/dreamrec/LivePilot/releases/latest) and double-click it.
554
568
  Claude Desktop installs everything automatically. Then:
555
569
 
556
570
  1. Open Ableton Live 12
@@ -584,10 +598,14 @@ Restart Ableton → Preferences → Link, Tempo & MIDI → Control Surface → *
584
598
  <details>
585
599
  <summary><strong>2. MCP Client</strong></summary>
586
600
 
587
- **Claude Code:**
601
+ **Claude Code** — pick one (installing both registers the MCP server twice and collides on port 9878):
588
602
  ```bash
603
+ # MCP server only:
589
604
  claude mcp add LivePilot -- npx livepilot
590
- claude plugin add github:dreamrec/LivePilot/plugin
605
+
606
+ # Or the full plugin (MCP server + skills + slash commands):
607
+ claude plugin marketplace add github:dreamrec/LivePilot
608
+ claude plugin install livepilot@dreamrec-LivePilot
591
609
  ```
592
610
 
593
611
  **Codex App:**
@@ -680,7 +698,8 @@ npx livepilot --install-codex-plugin
680
698
  **Claude Code**
681
699
 
682
700
  ```bash
683
- claude plugin add github:dreamrec/LivePilot/plugin
701
+ claude plugin marketplace add github:dreamrec/LivePilot
702
+ claude plugin install livepilot@dreamrec-LivePilot
684
703
  ```
685
704
 
686
705
  | Command | What |
@@ -732,7 +751,7 @@ npx livepilot --version # Show version
732
751
  | Requirement | Minimum |
733
752
  |-------------|---------|
734
753
  | Ableton Live | **12** (any edition). Suite required for Max for Live bridge and stock instruments |
735
- | Python | 3.9+ |
754
+ | Python | 3.11+ (numpy/scipy ship no wheels for 3.9/3.10) |
736
755
  | Node.js | 18+ |
737
756
  | OS | macOS / Windows |
738
757
  | Splice | Desktop app with downloaded samples (optional — enables SQLite metadata search) |
package/bin/livepilot.js CHANGED
@@ -15,6 +15,13 @@ const REQUIREMENTS = path.join(ROOT, "requirements.txt");
15
15
  // Python detection
16
16
  // ---------------------------------------------------------------------------
17
17
 
18
+ // Minimum Python is 3.11, NOT 3.9: numpy>=2.4.6 and scipy>=1.17.1 (see
19
+ // requirements.txt) publish no wheels and declare requires-python ">=3.11".
20
+ // A 3.9/3.10 interpreter passes a looser gate, the venv is created, then
21
+ // `pip install` aborts with a cryptic "no matching distribution" — the exact
22
+ // install failure users hit. Gate here so we fail early with a clear message.
23
+ const MIN_PY_MINOR = 11;
24
+
18
25
  function findPython() {
19
26
  // On Windows, also try the "py -3" launcher which avoids the
20
27
  // Microsoft Store stub that "python3" resolves to.
@@ -22,6 +29,7 @@ function findPython() {
22
29
  ? ["python", "python3", "py"]
23
30
  : ["python3", "python"];
24
31
 
32
+ let tooOld = null; // highest 3.x below the floor we saw, for a clear error
25
33
  for (const cmd of candidates) {
26
34
  try {
27
35
  const args = cmd === "py" ? ["-3", "--version"] : ["--version"];
@@ -33,17 +41,26 @@ function findPython() {
33
41
  if (match) {
34
42
  const major = parseInt(match[1], 10);
35
43
  const minor = parseInt(match[2], 10);
36
- if (major === 3 && minor >= 9) {
44
+ if (major === 3 && minor >= MIN_PY_MINOR) {
37
45
  // For "py" launcher, the actual command to use is "py -3"
38
46
  const actualCmd = cmd === "py" ? "py" : cmd;
39
47
  const actualArgs = cmd === "py" ? ["-3"] : [];
40
48
  return { cmd: actualCmd, version: out, prefixArgs: actualArgs };
41
49
  }
50
+ // Found Python 3 but below 3.11 — remember the newest for diagnostics.
51
+ if (major === 3 && (!tooOld || minor > tooOld.minor)) {
52
+ tooOld = { version: out, minor };
53
+ }
42
54
  }
43
55
  } catch {
44
56
  // command not found or failed — try next
45
57
  }
46
58
  }
59
+ // Signal "present but too old" distinctly from "absent" so callers can give
60
+ // an actionable message instead of a misleading "not found".
61
+ if (tooOld) {
62
+ return { tooOld: true, version: tooOld.version };
63
+ }
47
64
  return null;
48
65
  }
49
66
 
@@ -84,6 +101,41 @@ function findOtherLiveClient(host, port) {
84
101
  // Virtual environment bootstrap
85
102
  // ---------------------------------------------------------------------------
86
103
 
104
+ /**
105
+ * Run `pip install -r requirements.txt` with captured stderr so a failure
106
+ * yields an actionable message instead of an opaque "Command failed".
107
+ */
108
+ function pipInstall(venvPy) {
109
+ // One pip resolver warning about grpcio-tools / protobuf<7 is EXPECTED and
110
+ // harmless: LivePilot imports only the pre-generated Splice stubs at runtime,
111
+ // never grpcio-tools. Pre-announce it so it doesn't read as a failure.
112
+ console.error(" (a single grpcio-tools/protobuf resolver warning is expected and safe)");
113
+ try {
114
+ execFileSync(venvPy, ["-m", "pip", "install", "-q", "-r", REQUIREMENTS], {
115
+ cwd: ROOT,
116
+ stdio: ["pipe", "pipe", "pipe"],
117
+ encoding: "utf-8",
118
+ timeout: 120000,
119
+ });
120
+ } catch (err) {
121
+ const stderr = (err && err.stderr ? String(err.stderr) : "").trim();
122
+ console.error("");
123
+ console.error("LivePilot: dependency installation failed.");
124
+ if (/No matching distribution|requires-python|Could not find a version/i.test(stderr)) {
125
+ console.error(" Most likely your Python is too old — LivePilot needs Python >= 3.11");
126
+ console.error(" (numpy/scipy ship no wheels for 3.9/3.10). Install 3.11+, delete the");
127
+ console.error(" .venv folder, and retry.");
128
+ }
129
+ const tail = stderr.split("\n").filter(Boolean).slice(-12);
130
+ if (tail.length) {
131
+ console.error("");
132
+ console.error(" pip output (last lines):");
133
+ for (const line of tail) console.error(" " + line);
134
+ }
135
+ throw err;
136
+ }
137
+ }
138
+
87
139
  /**
88
140
  * Ensure a local .venv exists with dependencies installed.
89
141
  * Returns the path to the venv Python binary.
@@ -104,11 +156,7 @@ function ensureVenv(systemPython, prefixArgs) {
104
156
  } catch {
105
157
  // venv exists but deps missing — reinstall
106
158
  console.error("LivePilot: reinstalling Python dependencies...");
107
- execFileSync(venvPy, ["-m", "pip", "install", "-q", "-r", REQUIREMENTS], {
108
- cwd: ROOT,
109
- stdio: ["pipe", "pipe", "inherit"],
110
- timeout: 120000,
111
- });
159
+ pipInstall(venvPy);
112
160
  return venvPy;
113
161
  }
114
162
  }
@@ -122,11 +170,7 @@ function ensureVenv(systemPython, prefixArgs) {
122
170
  });
123
171
 
124
172
  console.error("LivePilot: installing dependencies...");
125
- execFileSync(venvPython(), ["-m", "pip", "install", "-q", "-r", REQUIREMENTS], {
126
- cwd: ROOT,
127
- stdio: ["pipe", "pipe", "inherit"],
128
- timeout: 120000,
129
- });
173
+ pipInstall(venvPython());
130
174
 
131
175
  return venvPython();
132
176
  }
@@ -262,11 +306,15 @@ async function doctor() {
262
306
 
263
307
  // 1. Python
264
308
  const pyInfo = findPython();
265
- if (pyInfo) {
309
+ if (pyInfo && !pyInfo.tooOld) {
266
310
  console.log(" Python: %s (%s)", pyInfo.version, pyInfo.cmd);
311
+ } else if (pyInfo && pyInfo.tooOld) {
312
+ console.log(" Python: %s found, but LivePilot needs >= 3.11", pyInfo.version);
313
+ console.log(" Fix: install Python 3.11+ (numpy/scipy ship no wheels for 3.9/3.10)");
314
+ ok = false;
267
315
  } else {
268
- console.log(" Python: NOT FOUND (need >= 3.9)");
269
- console.log(" Fix: install Python 3.9+ and add to PATH");
316
+ console.log(" Python: NOT FOUND (need >= 3.11)");
317
+ console.log(" Fix: install Python 3.11+ and add to PATH");
270
318
  ok = false;
271
319
  }
272
320
 
@@ -686,10 +734,15 @@ async function setup() {
686
734
  // 1. Python
687
735
  console.log("Step 1/5: Checking Python...");
688
736
  const pyInfo = findPython();
689
- if (pyInfo) {
737
+ if (pyInfo && !pyInfo.tooOld) {
690
738
  console.log(" ✓ %s", pyInfo.version);
739
+ } else if (pyInfo && pyInfo.tooOld) {
740
+ console.log(" ✗ %s found, but LivePilot needs Python >= 3.11", pyInfo.version);
741
+ console.log(" (numpy/scipy ship no wheels for 3.9/3.10 — pip would fail)");
742
+ console.log(" Install: brew install python@3.12 (macOS) or python.org (Windows)");
743
+ ok = false;
691
744
  } else {
692
- console.log(" ✗ Python >= 3.9 not found");
745
+ console.log(" ✗ Python >= 3.11 not found");
693
746
  console.log(" Install: brew install python@3.12 (macOS) or python.org (Windows)");
694
747
  ok = false;
695
748
  }
@@ -722,7 +775,7 @@ async function setup() {
722
775
  // 3. Bootstrap Python venv
723
776
  console.log("");
724
777
  console.log("Step 3/5: Setting up Python environment...");
725
- if (pyInfo) {
778
+ if (pyInfo && !pyInfo.tooOld) {
726
779
  try {
727
780
  ensureVenv(pyInfo.cmd, pyInfo.prefixArgs);
728
781
  console.log(" ✓ Virtual environment ready");
@@ -730,6 +783,8 @@ async function setup() {
730
783
  console.log(" ✗ Failed: %s", err.message);
731
784
  ok = false;
732
785
  }
786
+ } else if (pyInfo && pyInfo.tooOld) {
787
+ console.log(" ⊘ Skipped (%s found, need Python >= 3.11)", pyInfo.version);
733
788
  } else {
734
789
  console.log(" ⊘ Skipped (no Python)");
735
790
  }
@@ -823,8 +878,10 @@ async function main() {
823
878
  console.log(" --help Show this help");
824
879
  console.log("");
825
880
  console.log("Environment:");
826
- console.log(" LIVE_MCP_HOST Remote Script host (default: 127.0.0.1)");
827
- console.log(" LIVE_MCP_PORT Remote Script port (default: 9878)");
881
+ console.log(" LIVE_MCP_HOST Remote Script host (default: 127.0.0.1)");
882
+ console.log(" LIVE_MCP_PORT Remote Script port (default: 9878)");
883
+ console.log(" LIVEPILOT_AUTO_INSTALL Auto-install Remote Script on launch (set to 'true')");
884
+ console.log(" LIVEPILOT_TCP_PORT Override Remote Script port (Desktop Extension/MCPB)");
828
885
  return;
829
886
  }
830
887
 
@@ -915,12 +972,18 @@ async function main() {
915
972
 
916
973
  // Default: start MCP server
917
974
  const pyInfo = findPython();
918
- if (!pyInfo) {
919
- console.error("Error: Python >= 3.9 is required but was not found.");
975
+ if (!pyInfo || pyInfo.tooOld) {
976
+ if (pyInfo && pyInfo.tooOld) {
977
+ console.error("Error: found %s, but LivePilot requires Python >= 3.11.", pyInfo.version);
978
+ console.error(" numpy>=2.4.6 and scipy>=1.17.1 publish no wheels for Python 3.9/3.10,");
979
+ console.error(" so dependency installation would fail. Install Python 3.11 or newer.");
980
+ } else {
981
+ console.error("Error: Python >= 3.11 is required but was not found.");
982
+ console.error(" Install Python 3.11+ and ensure 'python3' or 'python' is on your PATH.");
983
+ }
920
984
  console.error("");
921
- console.error("Install Python 3.9+ and ensure 'python3' or 'python' is on your PATH.");
922
985
  console.error(" macOS: brew install python@3.12");
923
- console.error(" Ubuntu: sudo apt install python3");
986
+ console.error(" Ubuntu: sudo apt install python3.12");
924
987
  console.error(" Windows: https://www.python.org/downloads/");
925
988
  process.exit(1);
926
989
  }
@@ -46,23 +46,32 @@ function _assertSafeInstallPath(resolvedPath, candidates) {
46
46
  "/Applications/Ableton",
47
47
  "C:\\ProgramData\\Ableton",
48
48
  ];
49
- // The detected Ableton candidate paths are always considered safe
49
+ // True iff `child` equals `root` or is a descendant of it. The path.sep
50
+ // suffix prevents `/Applications/Ableton-evil` from matching the
51
+ // `/Applications/Ableton` prefix (prefix-boundary check).
52
+ const within = (child, root) => {
53
+ const c = path.resolve(child);
54
+ const r = path.resolve(root);
55
+ return c === r || c.startsWith(r + path.sep);
56
+ };
57
+ // The destination must be a detected Ableton Remote Scripts dir, a descendant
58
+ // of one, or under a known-safe prefix. We deliberately do NOT treat an
59
+ // *ancestor* of a candidate as safe — that previously let
60
+ // LIVEPILOT_INSTALL_PATH=/ or /Applications slip through (candidate path
61
+ // "starts with" the broad parent), defeating the guard.
50
62
  for (const c of candidates) {
51
- if (path.resolve(c.path).startsWith(path.resolve(resolvedPath))) {
52
- return;
53
- }
54
- if (path.resolve(resolvedPath).startsWith(path.resolve(c.path))) {
63
+ if (within(resolvedPath, c.path)) {
55
64
  return;
56
65
  }
57
66
  }
58
- const safe = allowedPrefixes.some((p) => resolvedPath.startsWith(path.resolve(p)));
59
- if (!safe) {
60
- throw new InstallerAbort(
61
- `LIVEPILOT_INSTALL_PATH=${resolvedPath} is outside permitted directories. ` +
62
- `Refusing to install. Allowed roots: ${allowedPrefixes.join(", ")}`,
63
- { recoverable: false }
64
- );
67
+ if (allowedPrefixes.some((p) => within(resolvedPath, p))) {
68
+ return;
65
69
  }
70
+ throw new InstallerAbort(
71
+ `LIVEPILOT_INSTALL_PATH=${resolvedPath} is outside permitted directories. ` +
72
+ `Refusing to install. Allowed roots: ${allowedPrefixes.join(", ")}`,
73
+ { recoverable: false }
74
+ );
66
75
  }
67
76
 
68
77
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.27.0",
3
+ "version": "1.27.2",
4
4
  "description": "Agentic production system for Ableton Live 12 \u2014 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, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
5
  "author": {
6
6
  "name": "Pilot Studio"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.27.0",
3
+ "version": "1.27.2",
4
4
  "description": "Agentic production system for Ableton Live 12 \u2014 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, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
5
  "author": {
6
6
  "name": "Pilot Studio"
@@ -198,14 +198,14 @@ Use when the user wants options, variants, or is stuck ("surprise me", "try some
198
198
  **Rule of thumb**: if the user asked for a specific fix, Flow A. If they asked "what would you do?" or mentioned feel/vibe without parameters, Flow B.
199
199
 
200
200
  ### Semantic Moves
201
- High-level musical intents that compile to deterministic tool sequences. 7 families (44 moves as of v1.27.0):
202
- - **mix** — `tighten_low_end`, `widen_stereo`, `make_punchier`, `darken_without_losing_width`, `reduce_repetition_fatigue`, `make_kick_bass_lock`, `reduce_foreground_competition`
203
- - **arrangement** — `refresh_repeated_section`, plus structural moves defined alongside mix
204
- - **transition** — `create_buildup_tension`, `smooth_scene_handoff`, `increase_contrast_before_payoff`, `bridge_sections`, `increase_forward_motion`, `open_chorus`, `create_breakdown`
205
- - **sound_design** — `add_warmth`, `add_texture`, `shape_transients`, `add_space`
206
- - **performance** — `recover_energy`, `decompress_tension`, `safe_spotlight`, `emergency_simplify`
207
- - **device_creation** — `create_chaos_modulator`, `create_feedback_resonator`, `create_wavefolder_effect`, `create_bitcrusher_effect`, `create_karplus_string`, `create_stochastic_texture`, `create_fdn_reverb` (procedural M4L device generation)
208
- - **sample** — `sample_chop_rhythm`, `sample_texture_layer`, `sample_vocal_ghost`, `sample_break_layer`, `sample_resample_destroy`, `sample_one_shot_accent` (registered from `sample_engine/moves.py`)
201
+ High-level musical intents that compile to deterministic tool sequences. 7 families (44 moves as of v1.27.1):
202
+ - **mix** (9) — `tighten_low_end`, `widen_stereo`, `make_punchier`, `darken_without_losing_width`, `reduce_repetition_fatigue`, `make_kick_bass_lock`, `set_track_routing`, `set_track_metadata`, `configure_send_architecture`
203
+ - **device_creation** (9) — `create_chaos_modulator`, `create_feedback_resonator`, `create_wavefolder_effect`, `create_bitcrusher_effect`, `create_karplus_string`, `create_stochastic_texture`, `create_fdn_reverb`, `create_drum_rack_pad`, `build_send_chain` (procedural M4L device generation)
204
+ - **sound_design** (7) — `add_warmth`, `add_texture`, `shape_transients`, `add_space`, `configure_device`, `remove_device`, `load_chord_source`
205
+ - **sample** (6) — `sample_chop_rhythm`, `sample_texture_layer`, `sample_vocal_ghost`, `sample_break_layer`, `sample_resample_destroy`, `sample_one_shot_accent` (registered from `sample_engine/moves.py`)
206
+ - **performance** (5) — `recover_energy`, `decompress_tension`, `safe_spotlight`, `emergency_simplify`, `configure_record_readiness`
207
+ - **arrangement** (4) — `create_buildup_tension`, `smooth_scene_handoff`, `configure_groove`, `set_scene_metadata`
208
+ - **transition** (4) — `bridge_sections`, `increase_forward_motion`, `open_chorus`, `create_breakdown`
209
209
 
210
210
  Use `list_semantic_moves(domain="mix")` to discover available moves.
211
211
 
@@ -1,4 +1,4 @@
1
- # LivePilot v1.27.0 — Architecture & Tool Reference
1
+ # LivePilot v1.27.2 — Architecture & Tool Reference
2
2
 
3
3
  Agentic production system for Ableton Live 12. 467 tools across 56 domains. Device atlas (5264 devices, 120 enriched, 47 with aesthetic-tagged `signature_techniques`), spectral perception (M4L analyzer with 9-band FFT — sub_low / sub / low / low_mid / mid / high_mid / high / presence / air), technique memory, automation intelligence (16 curve types, 15 recipes), music theory (Krumhansl-Schmuckler, species counterpoint), generative algorithms (Euclidean rhythm, tintinnabuli, phase shift, additive process), neo-Riemannian harmony (PRL transforms, Tonnetz), MIDI file I/O, **LIVE Splice describe-a-sound + variations via captured GraphQL endpoints (v1.17)**, drum-rack pad-by-pad construction, live dead-device detection via meter sampling, role-aware Simpler defaults, session-record arrangement-automation workaround.
4
4
 
@@ -218,7 +218,7 @@ This turns "set EQ band 3 to -4 dB" into "cut 400 Hz by 4 dB, then read the spec
218
218
  | `memory_update` | Updates name, tags, or qualities | `technique_id`, `name`, `tags`, `qualities` |
219
219
  | `memory_delete` | Removes technique (backs up first) | `technique_id` |
220
220
 
221
- ### Analyzer (30) — Real-time DSP analysis (requires LivePilot Analyzer M4L device on master track)
221
+ ### Analyzer (38) — Real-time DSP analysis (requires LivePilot Analyzer M4L device on master track)
222
222
 
223
223
  | Tool | What it does | Key params |
224
224
  |------|-------------|------------|
@@ -173,7 +173,7 @@ Live 12.4 introduces a new capability tier that unlocks native LOM access for sa
173
173
  **Tool signatures:** unchanged. Callers do not need to detect the tier —
174
174
  routing is transparent.
175
175
 
176
- **Probe-first 12.4 surfaces in v1.27.0:**
176
+ **Probe-first 12.4 surfaces in v1.27.2:**
177
177
  - `probe_link_audio()` reports observed peer/input/routing visibility and
178
178
  returns `manual_only`, `readable`, `routable`, or `unavailable`.
179
179
  - `probe_stem_workflow()` reports observed callable paths and returns
Binary file
@@ -34,7 +34,7 @@ outlets = 2; // 0: to udpsend (responses), 1: to buffer~/status
34
34
  // Single source of truth for the bridge version — bumped alongside the
35
35
  // rest of the release manifest. Surfaced in the UI via messnamed("livepilot_version", ...)
36
36
  // so the frozen .amxd visibly reports which build it was last exported from.
37
- var VERSION = "1.27.0";
37
+ var VERSION = "1.27.2";
38
38
 
39
39
  // ── State ──────────────────────────────────────────────────────────────────
40
40
 
@@ -45,6 +45,7 @@ var pitch_history = []; // Rolling buffer for key detection
45
45
  var MAX_PITCH_HISTORY = 128;
46
46
  var detected_key = "";
47
47
  var detected_scale = "";
48
+ var current_response_request_id = "";
48
49
 
49
50
  // Capture state
50
51
  var capture_active = false;
@@ -85,9 +86,19 @@ function anything() {
85
86
  var cmd = messagename;
86
87
  if (cmd.charAt(0) === "/") cmd = cmd.substring(1);
87
88
  var args = _decode_arg_strings(arrayfromargs(arguments));
89
+ var request_id = "";
90
+ if (args.length > 0 && typeof args[args.length - 1] === "string") {
91
+ var last_arg = args[args.length - 1];
92
+ var request_prefix = "__livepilot_request_id:";
93
+ if (last_arg.indexOf(request_prefix) === 0) {
94
+ request_id = last_arg.substring(request_prefix.length);
95
+ args.pop();
96
+ }
97
+ }
88
98
 
89
99
  // Defer to low-priority thread for LiveAPI safety
90
100
  var task = new Task(function() {
101
+ current_response_request_id = request_id;
91
102
  try {
92
103
  dispatch(cmd, args);
93
104
  } catch(e) {
@@ -223,6 +234,11 @@ function cmd_get_params(args) {
223
234
 
224
235
  var batch_size = 8;
225
236
  var current = 0;
237
+ // Capture the request id now: read_batch reschedules itself via
238
+ // Task.schedule(20), and an interleaved command arriving in that gap can
239
+ // overwrite the module-level current_response_request_id before the final
240
+ // send_response fires. The closure keeps OUR id stable across batches.
241
+ var captured_request_id = current_response_request_id;
226
242
 
227
243
  function read_batch() {
228
244
  try {
@@ -246,7 +262,7 @@ function cmd_get_params(args) {
246
262
  var next_task = new Task(read_batch);
247
263
  next_task.schedule(20);
248
264
  } else {
249
- send_response({"track": track_idx, "device": device_idx, "params": params});
265
+ send_response({"track": track_idx, "device": device_idx, "params": params}, captured_request_id);
250
266
  }
251
267
  } catch (e) {
252
268
  send_response({
@@ -254,7 +270,7 @@ function cmd_get_params(args) {
254
270
  "track": track_idx,
255
271
  "device": device_idx,
256
272
  "partial_params": params
257
- });
273
+ }, captured_request_id);
258
274
  }
259
275
  }
260
276
 
@@ -275,6 +291,9 @@ function cmd_get_hidden_params(args) {
275
291
  var params = [];
276
292
  var current = 0;
277
293
  var batch_size = 8;
294
+ // See cmd_get_params: capture the request id so the closure survives the
295
+ // Task.schedule(20) gaps without being clobbered by an interleaved command.
296
+ var captured_request_id = current_response_request_id;
278
297
 
279
298
  function read_batch() {
280
299
  try {
@@ -307,7 +326,7 @@ function cmd_get_hidden_params(args) {
307
326
  "device_name": device_name,
308
327
  "total_params": param_count,
309
328
  "params": params
310
- });
329
+ }, captured_request_id);
311
330
  }
312
331
  } catch (e) {
313
332
  send_response({
@@ -316,7 +335,7 @@ function cmd_get_hidden_params(args) {
316
335
  "device": device_idx,
317
336
  "device_name": device_name,
318
337
  "partial_params": params
319
- });
338
+ }, captured_request_id);
320
339
  }
321
340
  }
322
341
 
@@ -334,6 +353,9 @@ function cmd_get_auto_state(args) {
334
353
  var results = [];
335
354
  var current = 0;
336
355
  var batch_size = 8;
356
+ // See cmd_get_params: capture the request id so the closure survives the
357
+ // Task.schedule(20) gaps without being clobbered by an interleaved command.
358
+ var captured_request_id = current_response_request_id;
337
359
 
338
360
  function read_batch() {
339
361
  try {
@@ -362,7 +384,7 @@ function cmd_get_auto_state(args) {
362
384
  "total_params": param_count,
363
385
  "automated_params": results,
364
386
  "automated_count": results.length
365
- });
387
+ }, captured_request_id);
366
388
  }
367
389
  } catch (e) {
368
390
  send_response({
@@ -372,7 +394,7 @@ function cmd_get_auto_state(args) {
372
394
  "total_params": param_count,
373
395
  "automated_params": results,
374
396
  "automated_count": results.length
375
- });
397
+ }, captured_request_id);
376
398
  }
377
399
  }
378
400
 
@@ -689,22 +711,37 @@ function correlate(a, b) {
689
711
 
690
712
  // ── Response Encoding ──────────────────────────────────────────────────────
691
713
 
692
- function send_response(obj) {
693
- var json = JSON.stringify(obj);
714
+ function send_response(obj, explicit_id) {
715
+ var response = obj || {};
716
+ // Async/batched handlers (cmd_get_params et al.) capture the request id in
717
+ // a closure and pass it explicitly here, because the module-level
718
+ // current_response_request_id can be clobbered by an interleaved command
719
+ // that arrives during a Task.schedule() gap between batches.
720
+ var rid = (typeof explicit_id === "string" && explicit_id !== "")
721
+ ? explicit_id
722
+ : current_response_request_id;
723
+ if (rid) {
724
+ response._livepilot_request_id = rid;
725
+ }
726
+ var json = JSON.stringify(response);
694
727
  var encoded = base64_encode(json);
695
728
 
696
729
  // Check if chunking needed (Max OSC packet limit ~8KB)
697
730
  if (encoded.length < 1400) {
698
731
  outlet(0, "/response", encoded);
699
732
  } else {
700
- // Split into chunks
733
+ // Split into chunks. The request id rides the chunk header (first arg)
734
+ // so the Python side buckets chunks per-response and never mixes two
735
+ // commands' chunks — the id inside the JSON can't be read until the
736
+ // chunks are reassembled.
701
737
  var chunk_size = 1400;
702
738
  var total = Math.ceil(encoded.length / chunk_size);
703
739
  for (var i = 0; i < total; i++) {
704
740
  var piece = encoded.substring(i * chunk_size, (i + 1) * chunk_size);
705
- outlet(0, "/response_chunk", i, total, piece);
741
+ outlet(0, "/response_chunk", (rid || ""), i, total, piece);
706
742
  }
707
743
  }
744
+ current_response_request_id = "";
708
745
  }
709
746
 
710
747
  function base64_encode(str) {
@@ -1,2 +1,2 @@
1
1
  """LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
2
- __version__ = "1.27.0"
2
+ __version__ = "1.27.2"