livepilot 1.27.1 → 1.27.3

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 (117) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +10 -7
  3. package/bin/livepilot.js +156 -37
  4. package/livepilot/.Codex-plugin/plugin.json +1 -1
  5. package/livepilot/.claude-plugin/plugin.json +1 -1
  6. package/livepilot/skills/livepilot-core/SKILL.md +33 -1
  7. package/livepilot/skills/livepilot-core/references/atlas-tool-notes.md +69 -0
  8. package/livepilot/skills/livepilot-core/references/overview.md +18 -11
  9. package/livepilot/skills/livepilot-core/references/perception.md +125 -0
  10. package/livepilot/skills/livepilot-devices/references/device-parameter-units.md +66 -0
  11. package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
  12. package/livepilot/skills/livepilot-mix-engine/SKILL.md +8 -0
  13. package/livepilot/skills/livepilot-release/SKILL.md +1 -1
  14. package/livepilot/skills/livepilot-sample-engine/references/splice-tools-notes.md +56 -0
  15. package/livepilot/skills/livepilot-wonder/SKILL.md +6 -0
  16. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  17. package/m4l_device/livepilot_bridge.js +48 -11
  18. package/mcp_server/__init__.py +1 -1
  19. package/mcp_server/atlas/__init__.py +181 -40
  20. package/mcp_server/atlas/overlays.py +46 -3
  21. package/mcp_server/atlas/tools.py +226 -86
  22. package/mcp_server/audit/checks.py +50 -22
  23. package/mcp_server/audit/state.py +10 -2
  24. package/mcp_server/audit/tools.py +27 -6
  25. package/mcp_server/composer/develop/apply.py +7 -7
  26. package/mcp_server/composer/fast/apply.py +29 -23
  27. package/mcp_server/composer/fast/brief_builder.py +6 -2
  28. package/mcp_server/composer/framework/atlas_resolver.py +16 -1
  29. package/mcp_server/composer/full/apply.py +40 -34
  30. package/mcp_server/composer/full/engine.py +66 -32
  31. package/mcp_server/composer/tools.py +16 -9
  32. package/mcp_server/connection.py +69 -10
  33. package/mcp_server/creative_constraints/engine.py +10 -2
  34. package/mcp_server/creative_constraints/tools.py +24 -7
  35. package/mcp_server/curves.py +30 -7
  36. package/mcp_server/device_forge/builder.py +40 -11
  37. package/mcp_server/device_forge/models.py +9 -0
  38. package/mcp_server/device_forge/tools.py +30 -11
  39. package/mcp_server/experiment/engine.py +29 -22
  40. package/mcp_server/experiment/tools.py +15 -5
  41. package/mcp_server/m4l_bridge.py +217 -62
  42. package/mcp_server/memory/taste_graph.py +43 -0
  43. package/mcp_server/memory/technique_store.py +26 -3
  44. package/mcp_server/memory/tools.py +62 -4
  45. package/mcp_server/mix_engine/critics.py +187 -30
  46. package/mcp_server/mix_engine/models.py +21 -1
  47. package/mcp_server/mix_engine/state_builder.py +87 -8
  48. package/mcp_server/mix_engine/tools.py +16 -4
  49. package/mcp_server/performance_engine/tools.py +56 -8
  50. package/mcp_server/persistence/base_store.py +11 -0
  51. package/mcp_server/persistence/project_store.py +132 -8
  52. package/mcp_server/persistence/taste_store.py +90 -7
  53. package/mcp_server/preview_studio/engine.py +40 -10
  54. package/mcp_server/preview_studio/models.py +40 -0
  55. package/mcp_server/preview_studio/tools.py +56 -12
  56. package/mcp_server/project_brain/arrangement_graph.py +4 -0
  57. package/mcp_server/project_brain/models.py +2 -0
  58. package/mcp_server/project_brain/role_graph.py +13 -7
  59. package/mcp_server/reference_engine/gap_analyzer.py +58 -3
  60. package/mcp_server/reference_engine/profile_builder.py +47 -4
  61. package/mcp_server/reference_engine/tools.py +6 -0
  62. package/mcp_server/runtime/execution_router.py +57 -1
  63. package/mcp_server/runtime/mcp_dispatch.py +22 -0
  64. package/mcp_server/runtime/remote_commands.py +9 -4
  65. package/mcp_server/runtime/tools.py +0 -1
  66. package/mcp_server/sample_engine/sources.py +0 -2
  67. package/mcp_server/sample_engine/tools.py +19 -38
  68. package/mcp_server/semantic_moves/mix_compilers.py +276 -51
  69. package/mcp_server/semantic_moves/performance_compilers.py +51 -17
  70. package/mcp_server/semantic_moves/resolvers.py +45 -0
  71. package/mcp_server/semantic_moves/sound_design_compilers.py +14 -6
  72. package/mcp_server/semantic_moves/tools.py +80 -2
  73. package/mcp_server/semantic_moves/transition_compilers.py +26 -9
  74. package/mcp_server/server.py +27 -25
  75. package/mcp_server/session_continuity/tracker.py +51 -3
  76. package/mcp_server/song_brain/builder.py +47 -5
  77. package/mcp_server/song_brain/tools.py +21 -7
  78. package/mcp_server/sound_design/critics.py +1 -0
  79. package/mcp_server/splice_client/client.py +117 -33
  80. package/mcp_server/splice_client/http_bridge.py +15 -3
  81. package/mcp_server/splice_client/quota.py +28 -0
  82. package/mcp_server/stuckness_detector/detector.py +8 -5
  83. package/mcp_server/synthesis_brain/adapters/drift.py +13 -0
  84. package/mcp_server/synthesis_brain/adapters/meld.py +13 -0
  85. package/mcp_server/tools/_analyzer_engine/sample.py +36 -10
  86. package/mcp_server/tools/_perception_engine.py +6 -0
  87. package/mcp_server/tools/agent_os.py +4 -1
  88. package/mcp_server/tools/analyzer.py +198 -209
  89. package/mcp_server/tools/arrangement.py +7 -4
  90. package/mcp_server/tools/automation.py +24 -4
  91. package/mcp_server/tools/browser.py +25 -11
  92. package/mcp_server/tools/clips.py +6 -0
  93. package/mcp_server/tools/composition.py +33 -2
  94. package/mcp_server/tools/devices.py +53 -53
  95. package/mcp_server/tools/generative.py +14 -14
  96. package/mcp_server/tools/harmony.py +7 -7
  97. package/mcp_server/tools/mixing.py +4 -4
  98. package/mcp_server/tools/planner.py +68 -6
  99. package/mcp_server/tools/research.py +20 -2
  100. package/mcp_server/tools/theory.py +10 -10
  101. package/mcp_server/tools/transport.py +7 -2
  102. package/mcp_server/transition_engine/critics.py +13 -1
  103. package/mcp_server/user_corpus/tools.py +30 -1
  104. package/mcp_server/wonder_mode/engine.py +82 -9
  105. package/mcp_server/wonder_mode/session.py +32 -10
  106. package/mcp_server/wonder_mode/tools.py +14 -1
  107. package/package.json +1 -1
  108. package/remote_script/LivePilot/__init__.py +21 -8
  109. package/remote_script/LivePilot/arrangement.py +93 -33
  110. package/remote_script/LivePilot/browser.py +60 -4
  111. package/remote_script/LivePilot/devices.py +132 -62
  112. package/remote_script/LivePilot/mixing.py +31 -5
  113. package/remote_script/LivePilot/server.py +94 -22
  114. package/remote_script/LivePilot/tracks.py +11 -5
  115. package/remote_script/LivePilot/transport.py +11 -0
  116. package/requirements.txt +5 -5
  117. package/server.json +2 -2
@@ -17,6 +17,7 @@ resulting in silent playback. The hygiene here fixes both.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import asyncio
20
21
  import logging
21
22
  import os
22
23
  import re
@@ -86,10 +87,14 @@ _ONESHOT_HINTS = (
86
87
  "/one_shots/",
87
88
  "/one-shots/",
88
89
  )
89
- _LOOP_FILENAME_RE = re.compile(
90
- r"(?:_|\b)\d{2,3}(?:_|bpm|\b)|(?:_|\b)loop(?:_|\b)",
91
- re.IGNORECASE,
92
- )
90
+ # The "loop" word is an unambiguous loop signal.
91
+ _LOOP_TOKEN_RE = re.compile(r"(?:_|\b)loop(?:_|\b)", re.IGNORECASE)
92
+ # A bare 2-3 digit number is an AMBIGUOUS signal — it matches Splice bare-BPM
93
+ # names ("pluck_124_Cmin") but ALSO drum-machine model numbers / indices in
94
+ # one-shot names ("Kick_808", "snare_05", "tom_120").
95
+ _BARE_NUMBER_RE = re.compile(r"(?:_|\b)(\d{2,3})(?:_|bpm|\b)", re.IGNORECASE)
96
+ # Drum-machine model numbers are gear identifiers, NEVER tempos.
97
+ _DRUM_MACHINE_MODELS = frozenset({808, 909, 707, 606, 505, 727, 626, 303, 333, 555})
93
98
 
94
99
 
95
100
  def _is_warped_loop(file_path: str) -> bool:
@@ -113,16 +118,34 @@ def _is_warped_loop(file_path: str) -> bool:
113
118
  return False
114
119
 
115
120
  stem = os.path.splitext(os.path.basename(file_path))[0]
121
+
122
+ # Explicit, unambiguous loop signals win first — genuine drum/melodic loops
123
+ # almost always carry one of these (a "125bpm" literal, a "loop" token, or
124
+ # a loops folder), so honoring them keeps the 2026-05-01 broadening intact.
116
125
  if _BPM_IN_FILENAME_RE.search(stem):
117
126
  return True
118
- if _LOOP_FILENAME_RE.search(stem):
127
+ if _LOOP_TOKEN_RE.search(stem):
119
128
  return True
120
129
  # Append trailing slash so `/loops/` and `/drum_loops/` match the
121
130
  # last directory component cleanly (os.path.dirname strips trailing /).
122
131
  parent = os.path.dirname(file_path).lower() + "/"
123
132
  if any(seg in parent for seg in _LOOP_PATH_HINTS):
124
133
  return True
125
- return False
134
+
135
+ # Only a bare 2-3 digit number remains. This is where drum ONE-SHOTS were
136
+ # being misclassified as warped loops (Kick_808, snare_05, tom_120), which
137
+ # then skipped the drum-root Transpose (-> plays 2 octaves down), force-
138
+ # looped, and warped them — the documented recurring drum-Simpler bug.
139
+ # A drum-machine model number is never a tempo; a bare number on a
140
+ # drum-material name with no explicit loop signal is a one-shot, not a loop.
141
+ m = _BARE_NUMBER_RE.search(stem)
142
+ if not m:
143
+ return False
144
+ if int(m.group(1)) in _DRUM_MACHINE_MODELS:
145
+ return False
146
+ if _detect_drum_root_note(file_path) is not None:
147
+ return False
148
+ return True
126
149
 
127
150
 
128
151
  def _filename_stem(file_path: str) -> str:
@@ -181,8 +204,11 @@ async def _simpler_post_load_hygiene(
181
204
 
182
205
  # Step 1: verify device name matches expected file
183
206
  try:
184
- track_info = ableton.send_command(
185
- "get_track_info", {"track_index": track_index}
207
+ # send_command is blocking socket I/O; this is an async tool, so offload
208
+ # to a worker thread to avoid freezing the event loop (and the bridge
209
+ # UDP endpoint + all other concurrent handlers) for the round-trip.
210
+ track_info = await asyncio.to_thread(
211
+ ableton.send_command, "get_track_info", {"track_index": track_index}
186
212
  )
187
213
  except Exception as exc:
188
214
  return {"error": f"Verification read failed: {exc}"}
@@ -276,7 +302,7 @@ async def _simpler_post_load_hygiene(
276
302
  )
277
303
 
278
304
  try:
279
- ableton.send_command("batch_set_parameters", {
305
+ await asyncio.to_thread(ableton.send_command, "batch_set_parameters", {
280
306
  "track_index": track_index,
281
307
  "device_index": device_index,
282
308
  "parameters": hygiene_params,
@@ -293,7 +319,7 @@ async def _simpler_post_load_hygiene(
293
319
  # to Slice/One-Shot explicitly if they want.
294
320
  playback_mode_set = False
295
321
  try:
296
- ableton.send_command("set_simpler_playback_mode", {
322
+ await asyncio.to_thread(ableton.send_command, "set_simpler_playback_mode", {
297
323
  "track_index": track_index,
298
324
  "device_index": device_index,
299
325
  "playback_mode": 0, # 0 = Classic, 1 = One-Shot, 2 = Slice
@@ -413,6 +413,12 @@ def compare_to_reference(
413
413
  "stereo_width_mix": stereo_width_mix,
414
414
  "stereo_width_ref": stereo_width_ref,
415
415
  "band_deltas": band_deltas,
416
+ # Absolute per-band energy balances (NOT deltas). The reference
417
+ # builder needs the reference's OWN band_balance to populate a
418
+ # ReferenceProfile; band_deltas (mix - ref) is the wrong scale for
419
+ # that. Kept alongside band_deltas so existing callers are unaffected.
420
+ "mix_band_balance": dict(mix_bands),
421
+ "reference_band_balance": dict(ref_bands),
416
422
  "suggestions": suggestions,
417
423
  }
418
424
 
@@ -9,6 +9,7 @@ These tools power the Agent OS cyclical loop:
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ import asyncio
12
13
  import json
13
14
  import logging
14
15
  from typing import Optional
@@ -612,7 +613,9 @@ async def iterate_toward_goal(
612
613
  if move is None:
613
614
  branch.status = "failed"
614
615
  continue
615
- session_info = ableton.send_command("get_session_info")
616
+ session_info = await asyncio.to_thread(
617
+ ableton.send_command, "get_session_info"
618
+ )
616
619
  kernel = {"session_info": session_info, "mode": "explore"}
617
620
  plan = compiler.compile(move, kernel)
618
621
  branch.compiled_plan = plan.to_dict()