livepilot 1.9.24 → 1.10.1

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 (185) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/AGENTS.md +3 -3
  3. package/CHANGELOG.md +223 -0
  4. package/CONTRIBUTING.md +2 -2
  5. package/LICENSE +62 -21
  6. package/README.md +291 -276
  7. package/bin/livepilot.js +87 -0
  8. package/installer/codex.js +147 -0
  9. package/livepilot/.Codex-plugin/plugin.json +2 -2
  10. package/livepilot/.claude-plugin/plugin.json +2 -2
  11. package/livepilot/skills/livepilot-arrangement/SKILL.md +18 -1
  12. package/livepilot/skills/livepilot-core/SKILL.md +22 -5
  13. package/livepilot/skills/livepilot-core/references/device-knowledge/00-index.md +34 -0
  14. package/livepilot/skills/livepilot-core/references/device-knowledge/automation-as-music.md +204 -0
  15. package/livepilot/skills/livepilot-core/references/device-knowledge/chains-genre.md +173 -0
  16. package/livepilot/skills/livepilot-core/references/device-knowledge/creative-thinking.md +211 -0
  17. package/livepilot/skills/livepilot-core/references/device-knowledge/effects-distortion.md +188 -0
  18. package/livepilot/skills/livepilot-core/references/device-knowledge/effects-space.md +162 -0
  19. package/livepilot/skills/livepilot-core/references/device-knowledge/effects-spectral.md +229 -0
  20. package/livepilot/skills/livepilot-core/references/device-knowledge/instruments-synths.md +243 -0
  21. package/livepilot/skills/livepilot-core/references/overview.md +13 -9
  22. package/livepilot/skills/livepilot-core/references/sample-manipulation.md +724 -0
  23. package/livepilot/skills/livepilot-core/references/sound-design-deep.md +140 -0
  24. package/livepilot/skills/livepilot-devices/SKILL.md +39 -4
  25. package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
  26. package/livepilot/skills/livepilot-release/SKILL.md +23 -19
  27. package/livepilot/skills/livepilot-sample-engine/SKILL.md +105 -0
  28. package/livepilot/skills/livepilot-sample-engine/references/sample-critics.md +87 -0
  29. package/livepilot/skills/livepilot-sample-engine/references/sample-philosophy.md +51 -0
  30. package/livepilot/skills/livepilot-sample-engine/references/sample-techniques.md +131 -0
  31. package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +45 -0
  32. package/livepilot/skills/livepilot-wonder/SKILL.md +17 -0
  33. package/livepilot.mcpb +0 -0
  34. package/m4l_device/livepilot_bridge.js +1 -1
  35. package/manifest.json +4 -4
  36. package/mcp_server/__init__.py +1 -1
  37. package/mcp_server/atlas/__init__.py +357 -0
  38. package/mcp_server/atlas/device_atlas.json +44067 -0
  39. package/mcp_server/atlas/enrichments/__init__.py +111 -0
  40. package/mcp_server/atlas/enrichments/audio_effects/auto_filter.yaml +162 -0
  41. package/mcp_server/atlas/enrichments/audio_effects/beat_repeat.yaml +183 -0
  42. package/mcp_server/atlas/enrichments/audio_effects/channel_eq.yaml +126 -0
  43. package/mcp_server/atlas/enrichments/audio_effects/chorus_ensemble.yaml +149 -0
  44. package/mcp_server/atlas/enrichments/audio_effects/color_limiter.yaml +109 -0
  45. package/mcp_server/atlas/enrichments/audio_effects/compressor.yaml +159 -0
  46. package/mcp_server/atlas/enrichments/audio_effects/convolution_reverb.yaml +143 -0
  47. package/mcp_server/atlas/enrichments/audio_effects/convolution_reverb_pro.yaml +178 -0
  48. package/mcp_server/atlas/enrichments/audio_effects/delay.yaml +151 -0
  49. package/mcp_server/atlas/enrichments/audio_effects/drum_buss.yaml +142 -0
  50. package/mcp_server/atlas/enrichments/audio_effects/dynamic_tube.yaml +147 -0
  51. package/mcp_server/atlas/enrichments/audio_effects/echo.yaml +167 -0
  52. package/mcp_server/atlas/enrichments/audio_effects/eq_eight.yaml +148 -0
  53. package/mcp_server/atlas/enrichments/audio_effects/eq_three.yaml +121 -0
  54. package/mcp_server/atlas/enrichments/audio_effects/erosion.yaml +103 -0
  55. package/mcp_server/atlas/enrichments/audio_effects/filter_delay.yaml +173 -0
  56. package/mcp_server/atlas/enrichments/audio_effects/gate.yaml +130 -0
  57. package/mcp_server/atlas/enrichments/audio_effects/gated_delay.yaml +133 -0
  58. package/mcp_server/atlas/enrichments/audio_effects/glue_compressor.yaml +142 -0
  59. package/mcp_server/atlas/enrichments/audio_effects/grain_delay.yaml +141 -0
  60. package/mcp_server/atlas/enrichments/audio_effects/hybrid_reverb.yaml +160 -0
  61. package/mcp_server/atlas/enrichments/audio_effects/limiter.yaml +97 -0
  62. package/mcp_server/atlas/enrichments/audio_effects/multiband_dynamics.yaml +174 -0
  63. package/mcp_server/atlas/enrichments/audio_effects/overdrive.yaml +119 -0
  64. package/mcp_server/atlas/enrichments/audio_effects/pedal.yaml +145 -0
  65. package/mcp_server/atlas/enrichments/audio_effects/phaser_flanger.yaml +161 -0
  66. package/mcp_server/atlas/enrichments/audio_effects/redux.yaml +114 -0
  67. package/mcp_server/atlas/enrichments/audio_effects/reverb.yaml +190 -0
  68. package/mcp_server/atlas/enrichments/audio_effects/roar.yaml +159 -0
  69. package/mcp_server/atlas/enrichments/audio_effects/saturator.yaml +146 -0
  70. package/mcp_server/atlas/enrichments/audio_effects/shifter.yaml +154 -0
  71. package/mcp_server/atlas/enrichments/audio_effects/spectral_resonator.yaml +141 -0
  72. package/mcp_server/atlas/enrichments/audio_effects/spectral_time.yaml +164 -0
  73. package/mcp_server/atlas/enrichments/audio_effects/vector_delay.yaml +140 -0
  74. package/mcp_server/atlas/enrichments/audio_effects/vinyl_distortion.yaml +141 -0
  75. package/mcp_server/atlas/enrichments/instruments/analog.yaml +222 -0
  76. package/mcp_server/atlas/enrichments/instruments/bass.yaml +202 -0
  77. package/mcp_server/atlas/enrichments/instruments/collision.yaml +150 -0
  78. package/mcp_server/atlas/enrichments/instruments/drift.yaml +167 -0
  79. package/mcp_server/atlas/enrichments/instruments/electric.yaml +137 -0
  80. package/mcp_server/atlas/enrichments/instruments/emit.yaml +163 -0
  81. package/mcp_server/atlas/enrichments/instruments/meld.yaml +164 -0
  82. package/mcp_server/atlas/enrichments/instruments/operator.yaml +197 -0
  83. package/mcp_server/atlas/enrichments/instruments/poli.yaml +192 -0
  84. package/mcp_server/atlas/enrichments/instruments/sampler.yaml +218 -0
  85. package/mcp_server/atlas/enrichments/instruments/simpler.yaml +217 -0
  86. package/mcp_server/atlas/enrichments/instruments/tension.yaml +156 -0
  87. package/mcp_server/atlas/enrichments/instruments/tree_tone.yaml +162 -0
  88. package/mcp_server/atlas/enrichments/instruments/vector_fm.yaml +165 -0
  89. package/mcp_server/atlas/enrichments/instruments/vector_grain.yaml +166 -0
  90. package/mcp_server/atlas/enrichments/instruments/wavetable.yaml +162 -0
  91. package/mcp_server/atlas/enrichments/midi_effects/arpeggiator.yaml +156 -0
  92. package/mcp_server/atlas/enrichments/midi_effects/bouncy_notes.yaml +93 -0
  93. package/mcp_server/atlas/enrichments/midi_effects/chord.yaml +147 -0
  94. package/mcp_server/atlas/enrichments/midi_effects/melodic_steps.yaml +97 -0
  95. package/mcp_server/atlas/enrichments/midi_effects/note_echo.yaml +108 -0
  96. package/mcp_server/atlas/enrichments/midi_effects/note_length.yaml +97 -0
  97. package/mcp_server/atlas/enrichments/midi_effects/pitch.yaml +76 -0
  98. package/mcp_server/atlas/enrichments/midi_effects/random.yaml +117 -0
  99. package/mcp_server/atlas/enrichments/midi_effects/rhythmic_steps.yaml +103 -0
  100. package/mcp_server/atlas/enrichments/midi_effects/scale.yaml +83 -0
  101. package/mcp_server/atlas/enrichments/midi_effects/step_arp.yaml +112 -0
  102. package/mcp_server/atlas/enrichments/midi_effects/velocity.yaml +119 -0
  103. package/mcp_server/atlas/enrichments/utility/amp.yaml +159 -0
  104. package/mcp_server/atlas/enrichments/utility/cabinet.yaml +109 -0
  105. package/mcp_server/atlas/enrichments/utility/corpus.yaml +150 -0
  106. package/mcp_server/atlas/enrichments/utility/resonators.yaml +131 -0
  107. package/mcp_server/atlas/enrichments/utility/spectrum.yaml +63 -0
  108. package/mcp_server/atlas/enrichments/utility/tuner.yaml +51 -0
  109. package/mcp_server/atlas/enrichments/utility/utility.yaml +136 -0
  110. package/mcp_server/atlas/enrichments/utility/vocoder.yaml +160 -0
  111. package/mcp_server/atlas/scanner.py +236 -0
  112. package/mcp_server/atlas/tools.py +224 -0
  113. package/mcp_server/composer/__init__.py +1 -0
  114. package/mcp_server/composer/engine.py +532 -0
  115. package/mcp_server/composer/layer_planner.py +427 -0
  116. package/mcp_server/composer/prompt_parser.py +329 -0
  117. package/mcp_server/composer/sample_resolver.py +153 -0
  118. package/mcp_server/composer/tools.py +211 -0
  119. package/mcp_server/connection.py +53 -8
  120. package/mcp_server/corpus/__init__.py +377 -0
  121. package/mcp_server/device_forge/__init__.py +1 -0
  122. package/mcp_server/device_forge/builder.py +377 -0
  123. package/mcp_server/device_forge/models.py +142 -0
  124. package/mcp_server/device_forge/templates.py +483 -0
  125. package/mcp_server/device_forge/tools.py +162 -0
  126. package/mcp_server/m4l_bridge.py +1 -0
  127. package/mcp_server/memory/taste_accessors.py +47 -0
  128. package/mcp_server/preview_studio/engine.py +9 -2
  129. package/mcp_server/preview_studio/tools.py +78 -35
  130. package/mcp_server/project_brain/tools.py +34 -0
  131. package/mcp_server/runtime/capability_probe.py +21 -2
  132. package/mcp_server/runtime/execution_router.py +184 -38
  133. package/mcp_server/runtime/live_version.py +102 -0
  134. package/mcp_server/runtime/mcp_dispatch.py +46 -0
  135. package/mcp_server/runtime/remote_commands.py +13 -5
  136. package/mcp_server/runtime/tools.py +66 -29
  137. package/mcp_server/sample_engine/__init__.py +1 -0
  138. package/mcp_server/sample_engine/analyzer.py +216 -0
  139. package/mcp_server/sample_engine/critics.py +390 -0
  140. package/mcp_server/sample_engine/models.py +193 -0
  141. package/mcp_server/sample_engine/moves.py +127 -0
  142. package/mcp_server/sample_engine/planner.py +186 -0
  143. package/mcp_server/sample_engine/slice_workflow.py +190 -0
  144. package/mcp_server/sample_engine/sources.py +540 -0
  145. package/mcp_server/sample_engine/techniques.py +908 -0
  146. package/mcp_server/sample_engine/tools.py +545 -0
  147. package/mcp_server/semantic_moves/__init__.py +3 -0
  148. package/mcp_server/semantic_moves/device_creation_moves.py +237 -0
  149. package/mcp_server/semantic_moves/mix_moves.py +8 -8
  150. package/mcp_server/semantic_moves/models.py +7 -7
  151. package/mcp_server/semantic_moves/performance_moves.py +4 -4
  152. package/mcp_server/semantic_moves/sample_compilers.py +377 -0
  153. package/mcp_server/semantic_moves/sound_design_moves.py +4 -4
  154. package/mcp_server/semantic_moves/tools.py +63 -10
  155. package/mcp_server/semantic_moves/transition_moves.py +4 -4
  156. package/mcp_server/server.py +71 -1
  157. package/mcp_server/session_continuity/tracker.py +4 -1
  158. package/mcp_server/sound_design/critics.py +89 -1
  159. package/mcp_server/splice_client/__init__.py +1 -0
  160. package/mcp_server/splice_client/client.py +347 -0
  161. package/mcp_server/splice_client/models.py +96 -0
  162. package/mcp_server/splice_client/protos/__init__.py +1 -0
  163. package/mcp_server/splice_client/protos/app_pb2.py +319 -0
  164. package/mcp_server/splice_client/protos/app_pb2.pyi +1153 -0
  165. package/mcp_server/splice_client/protos/app_pb2_grpc.py +1946 -0
  166. package/mcp_server/tools/_conductor.py +16 -0
  167. package/mcp_server/tools/_planner_engine.py +24 -0
  168. package/mcp_server/tools/analyzer.py +2 -0
  169. package/mcp_server/tools/arrangement.py +69 -0
  170. package/mcp_server/tools/automation.py +15 -2
  171. package/mcp_server/tools/devices.py +117 -6
  172. package/mcp_server/tools/notes.py +37 -4
  173. package/mcp_server/tools/planner.py +3 -0
  174. package/mcp_server/wonder_mode/diagnosis.py +5 -0
  175. package/mcp_server/wonder_mode/engine.py +144 -14
  176. package/mcp_server/wonder_mode/tools.py +33 -1
  177. package/package.json +14 -4
  178. package/remote_script/LivePilot/__init__.py +8 -1
  179. package/remote_script/LivePilot/arrangement.py +114 -0
  180. package/remote_script/LivePilot/browser.py +56 -1
  181. package/remote_script/LivePilot/devices.py +246 -6
  182. package/remote_script/LivePilot/mixing.py +8 -3
  183. package/remote_script/LivePilot/server.py +5 -1
  184. package/remote_script/LivePilot/transport.py +3 -0
  185. package/remote_script/LivePilot/version_detect.py +78 -0
@@ -25,11 +25,11 @@ def discover_moves(
25
25
  """Find semantic moves relevant to the request.
26
26
 
27
27
  Uses keyword scoring + optional taste reranking + constraint filtering.
28
- Returns full move dicts including compile_plan (via registry.get_move).
28
+ Returns full move dicts including plan_template (via registry.get_move).
29
29
  """
30
30
  from ..semantic_moves import registry
31
31
 
32
- all_moves = registry.list_moves() # returns to_dict() — no compile_plan
32
+ all_moves = registry.list_moves() # returns to_dict() — no plan_template
33
33
  if not all_moves:
34
34
  return []
35
35
 
@@ -77,7 +77,7 @@ def discover_moves(
77
77
 
78
78
  scored.sort(key=lambda x: -x[1])
79
79
 
80
- # Enrich with full compile_plan via get_move()
80
+ # Enrich with full plan_template via get_move()
81
81
  result = []
82
82
  for move_dict, score in scored:
83
83
  full_move = registry.get_move(move_dict["move_id"])
@@ -98,7 +98,7 @@ def discover_moves(
98
98
  for move in result:
99
99
  plan = {"steps": [
100
100
  {"action": step.get("tool", ""), **step}
101
- for step in (move.get("compile_plan") or [])
101
+ for step in (move.get("plan_template") or [])
102
102
  ]}
103
103
  validation = validate_plan_against_constraints(plan, active_constraints)
104
104
  if validation["valid"]:
@@ -137,9 +137,9 @@ def _with_envelope(move: dict, tier: str) -> dict:
137
137
  # ── Distinctness selection ───────────────────────────────────────
138
138
 
139
139
 
140
- def _compile_plan_shape(move: dict) -> frozenset[str]:
141
- """Extract the set of tool names from a move's compile_plan."""
142
- plan = move.get("compile_plan") or []
140
+ def _plan_template_shape(move: dict) -> frozenset[str]:
141
+ """Extract the set of tool names from a move's plan_template."""
142
+ plan = move.get("plan_template") or []
143
143
  return frozenset(step.get("tool", "") for step in plan if step.get("tool"))
144
144
 
145
145
 
@@ -147,7 +147,7 @@ def select_distinct_variants(scored_moves: list[dict]) -> list[dict]:
147
147
  """Select genuinely distinct moves for variant generation.
148
148
 
149
149
  Each selected move must differ from all previously selected moves by
150
- at least one of: move_id, family, or compile_plan shape.
150
+ at least one of: move_id, family, or plan_template shape.
151
151
  Returns 0-3 moves.
152
152
  """
153
153
  if not scored_moves:
@@ -160,7 +160,7 @@ def select_distinct_variants(scored_moves: list[dict]) -> list[dict]:
160
160
  for move in scored_moves:
161
161
  mid = move.get("move_id", "")
162
162
  family = move.get("family", "")
163
- shape = _compile_plan_shape(move)
163
+ shape = _plan_template_shape(move)
164
164
 
165
165
  # Skip duplicate move_ids
166
166
  if mid in used_ids:
@@ -190,14 +190,45 @@ _NOVELTY_LEVELS = {"safe": 0.25, "strong": 0.55, "unexpected": 0.85}
190
190
  _RISK_TO_EFFECT = {"low": "preserves", "medium": "evolves", "high": "contrasts"}
191
191
 
192
192
 
193
+ def _compile_variant_plan(move_dict: dict, kernel: dict | None) -> dict | None:
194
+ """Compile a move through the semantic compiler if possible.
195
+
196
+ Returns CompiledPlan.to_dict() or None if no compiler is registered.
197
+ """
198
+ if kernel is None:
199
+ return None
200
+
201
+ move_id = move_dict.get("move_id", "")
202
+ from ..semantic_moves.compiler import compile as sem_compile, _COMPILERS
203
+ from ..semantic_moves import registry
204
+
205
+ if move_id not in _COMPILERS:
206
+ return None
207
+
208
+ move_obj = registry.get_move(move_id)
209
+ if move_obj is None:
210
+ return None
211
+
212
+ try:
213
+ plan = sem_compile(move_obj, kernel)
214
+ return plan.to_dict()
215
+ except Exception:
216
+ return None
217
+
218
+
193
219
  def build_variant(
194
220
  label: str,
195
221
  move_dict: dict,
196
222
  song_brain: Optional[dict] = None,
197
223
  novelty_level: float = 0.5,
198
224
  variant_id: str = "",
225
+ kernel: dict | None = None,
199
226
  ) -> dict:
200
- """Build a variant dict from a real move + SongBrain context."""
227
+ """Build a variant dict from a real move + SongBrain context.
228
+
229
+ If kernel is provided, compiles the move through the semantic compiler
230
+ for an executable plan. Otherwise falls back to plan_template metadata.
231
+ """
201
232
  song_brain = song_brain or {}
202
233
  targets = move_dict.get("targets", {})
203
234
  protect = move_dict.get("protect", {})
@@ -226,6 +257,10 @@ def build_variant(
226
257
  if sacred and identity_effect == "preserves":
227
258
  why += f". Preserves {sacred[0].get('description', 'sacred elements')}"
228
259
 
260
+ # Compile through semantic compiler if kernel available
261
+ compiled = _compile_variant_plan(move_dict, kernel)
262
+ analytical = compiled is None
263
+
229
264
  return {
230
265
  "variant_id": variant_id,
231
266
  "label": label,
@@ -239,11 +274,11 @@ def build_variant(
239
274
  "novelty_level": novelty_level,
240
275
  "taste_fit": 0.5,
241
276
  "targets_snapshot": dict(targets),
242
- "compiled_plan": move_dict.get("compile_plan"),
277
+ "compiled_plan": compiled,
243
278
  "score": 0.0,
244
279
  "rank": 0,
245
280
  "score_breakdown": {},
246
- "analytical_only": False,
281
+ "analytical_only": analytical,
247
282
  "distinctness_reason": "",
248
283
  }
249
284
 
@@ -390,8 +425,86 @@ def _all_same_family(variants: list[dict]) -> bool:
390
425
  return len(families) <= 1 and len(variants) > 1
391
426
 
392
427
 
393
- # ── Pipeline orchestrator ────────────────────────────────────────
428
+ # ── Corpus intelligence enrichment ──────────────────────────────
429
+
430
+
431
+ def _get_corpus_hints(request_text: str, diagnosis: dict | None) -> dict | None:
432
+ """Query the corpus for creative hints relevant to the request.
433
+
434
+ Returns a dict with emotional_recipe, genre_chain, automation_density,
435
+ and technique_suggestions — or None if corpus is unavailable.
436
+ """
437
+ try:
438
+ from ..corpus import get_corpus
439
+ except ImportError:
440
+ return None
394
441
 
442
+ corpus = get_corpus()
443
+ if not corpus.emotional_recipes and not corpus.genre_chains:
444
+ return None
445
+
446
+ hints: dict = {}
447
+ request_lower = request_text.lower()
448
+
449
+ # Check for emotional keywords
450
+ _EMOTION_KEYWORDS = {
451
+ "warm": "warmth & comfort", "cold": "tension & anxiety",
452
+ "dark": "melancholy", "bright": "euphoria",
453
+ "aggressive": "danger", "soft": "warmth & comfort",
454
+ "anxious": "tension & anxiety", "nostalgic": "nostalgia",
455
+ "vast": "vastness", "ethereal": "vastness",
456
+ "sad": "melancholy", "happy": "euphoria",
457
+ "tension": "tension & anxiety", "release": "euphoria",
458
+ }
459
+ for keyword, emotion_key in _EMOTION_KEYWORDS.items():
460
+ if keyword in request_lower:
461
+ recipe = corpus.suggest_for_emotion(emotion_key)
462
+ if recipe:
463
+ hints["emotional_recipe"] = {
464
+ "emotion": recipe.emotion,
465
+ "technique_count": len(recipe.techniques),
466
+ "first_techniques": [t[:100] for t in recipe.techniques[:3]],
467
+ }
468
+ break
469
+
470
+ # Check for genre keywords
471
+ _GENRE_KEYWORDS = ["dub", "techno", "minimal", "ambient", "idm", "trap",
472
+ "sophie", "arca", "house", "trance", "drum and bass"]
473
+ for genre in _GENRE_KEYWORDS:
474
+ if genre in request_lower:
475
+ chain = corpus.get_genre_chain(genre)
476
+ if chain:
477
+ hints["genre_chain"] = {
478
+ "genre": chain.genre,
479
+ "devices": chain.devices[:5],
480
+ "description": chain.description[:120],
481
+ }
482
+ break
483
+
484
+ # Check for physical model keywords
485
+ _MATERIAL_KEYWORDS = ["water", "metal", "glass", "breath", "fire", "electric"]
486
+ for material in _MATERIAL_KEYWORDS:
487
+ if material in request_lower:
488
+ model = corpus.suggest_for_material(material)
489
+ if model:
490
+ hints["physical_model"] = {
491
+ "material": model.material,
492
+ "devices": model.devices[:4],
493
+ }
494
+ break
495
+
496
+ # Automation density from diagnosis section type
497
+ if diagnosis:
498
+ problem_class = diagnosis.get("problem_class", "")
499
+ if "static" in problem_class or "flat" in problem_class:
500
+ hints["automation_density"] = corpus.get_automation_density_for_section("peak")
501
+ elif "breakdown" in problem_class:
502
+ hints["automation_density"] = corpus.get_automation_density_for_section("breakdown")
503
+
504
+ return hints if hints else None
505
+
506
+
507
+ # ── Pipeline orchestrator ────────────────────────────────────────
395
508
 
396
509
 
397
510
  def generate_wonder_variants(
@@ -401,6 +514,8 @@ def generate_wonder_variants(
401
514
  song_brain: dict | None = None,
402
515
  taste_graph: object = None,
403
516
  active_constraints: object = None,
517
+ session_info: dict | None = None,
518
+ sample_context: dict | None = None,
404
519
  ) -> dict:
405
520
  """Full wonder mode pipeline: discover -> select distinct -> build -> taste -> rank."""
406
521
  song_brain = song_brain or {}
@@ -414,6 +529,17 @@ def generate_wonder_variants(
414
529
  labels = ["safe", "strong", "unexpected"]
415
530
  variants = []
416
531
 
532
+ # Load corpus intelligence for variant enrichment
533
+ corpus_hints = _get_corpus_hints(request_text, diagnosis)
534
+
535
+ # Build kernel for variant compilation
536
+ kernel = {
537
+ "session_info": session_info or {},
538
+ "mode": "improve",
539
+ }
540
+ if sample_context:
541
+ kernel.update(sample_context)
542
+
417
543
  # Build executable variants from distinct moves
418
544
  for i, move in enumerate(distinct):
419
545
  label = labels[i]
@@ -424,11 +550,15 @@ def generate_wonder_variants(
424
550
  song_brain=song_brain,
425
551
  novelty_level=_NOVELTY_LEVELS.get(label, 0.5),
426
552
  variant_id=f"{set_prefix}_{label}",
553
+ kernel=kernel,
427
554
  )
428
555
  if taste_graph is not None:
429
556
  # Score taste on envelope-adjusted move for consistency with targets_snapshot
430
557
  v["taste_fit"] = compute_taste_fit(move_with_envelope, taste_graph)
431
558
  v["distinctness_reason"] = _explain_distinctness(move, distinct, i)
559
+ # Enrich with corpus knowledge
560
+ if corpus_hints:
561
+ v["corpus_hints"] = corpus_hints
432
562
  variants.append(v)
433
563
 
434
564
  executable_count = len(variants)
@@ -483,7 +613,7 @@ def _explain_distinctness(move: dict, all_moves: list[dict], index: int) -> str:
483
613
 
484
614
  if family not in other_families:
485
615
  return f"Different family: {family}"
486
- shape = _compile_plan_shape(move)
616
+ shape = _plan_template_shape(move)
487
617
  return f"Different approach: {', '.join(sorted(shape))}"
488
618
 
489
619
 
@@ -126,14 +126,46 @@ def enter_wonder_mode(
126
126
  action_ledger=action_ledger,
127
127
  )
128
128
 
129
+ # 1b. If diagnosis includes sample domains, search for candidates
130
+ sample_context = {}
131
+ diag_dict = diagnosis.to_dict()
132
+ candidate_domains = diag_dict.get("candidate_domains") or []
133
+ if "sample" in candidate_domains:
134
+ try:
135
+ from ..sample_engine.tools import get_sample_opportunities, search_samples
136
+ opportunities = get_sample_opportunities(ctx)
137
+ if opportunities.get("opportunities"):
138
+ opp = opportunities["opportunities"][0]
139
+ query = opp.get("search_query", opp.get("description", "sample"))
140
+ results = search_samples(ctx, query=query, max_results=3)
141
+ candidates = results.get("results", [])
142
+ if candidates:
143
+ best = candidates[0]
144
+ sample_context["sample_file_path"] = best.get("file_path", "")
145
+ sample_context["sample_name"] = best.get("name", "")
146
+ sample_context["material_type"] = best.get("material_type", "")
147
+ except Exception:
148
+ pass # Graceful degradation — analytical variants still work
149
+
150
+ # 1c. Get session info for kernel
151
+ session_info = {}
152
+ try:
153
+ ableton = ctx.lifespan_context.get("ableton")
154
+ if ableton:
155
+ session_info = ableton.send_command("get_session_info", {})
156
+ except Exception:
157
+ pass
158
+
129
159
  # 2. Generate variants
130
160
  result = engine.generate_wonder_variants(
131
161
  request_text=request_text,
132
- diagnosis=diagnosis.to_dict(),
162
+ diagnosis=diag_dict,
133
163
  kernel_id=kernel_id,
134
164
  song_brain=song_brain,
135
165
  taste_graph=taste_graph,
136
166
  active_constraints=active_constraints,
167
+ session_info=session_info,
168
+ sample_context=sample_context,
137
169
  )
138
170
 
139
171
  # 3. Create WonderSession (unique per invocation, not deterministic)
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.9.24",
3
+ "version": "1.10.1",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "Agentic production system for Ableton Live 12 — 293 tools, 39 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
+ "description": "Agentic production system for Ableton Live 12 — 317 tools, 43 domains. Device atlas (1305 devices), sample engine (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, creative intelligence (12 engines)",
6
6
  "author": "Pilot Studio",
7
- "license": "MIT",
7
+ "license": "BSL-1.1",
8
8
  "type": "commonjs",
9
9
  "bin": {
10
10
  "livepilot": "./bin/livepilot.js"
@@ -17,6 +17,12 @@
17
17
  "bugs": {
18
18
  "url": "https://github.com/dreamrec/LivePilot/issues"
19
19
  },
20
+ "funding": [
21
+ {
22
+ "type": "github",
23
+ "url": "https://github.com/sponsors/dreamrec"
24
+ }
25
+ ],
20
26
  "keywords": [
21
27
  "mcp",
22
28
  "mcp-server",
@@ -29,7 +35,11 @@
29
35
  "ai",
30
36
  "sound-design",
31
37
  "mixing",
32
- "arrangement"
38
+ "arrangement",
39
+ "splice",
40
+ "sample-engine",
41
+ "auto-composition",
42
+ "device-atlas"
33
43
  ],
34
44
  "engines": {
35
45
  "node": ">=18.0.0"
@@ -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.9.24"
8
+ __version__ = "1.10.1"
9
9
 
10
10
  from _Framework.ControlSurface import ControlSurface
11
11
  from .server import LivePilotServer
@@ -20,6 +20,7 @@ from . import browser # noqa: F401 — registers browser handlers
20
20
  from . import arrangement # noqa: F401 — registers arrangement handlers
21
21
  from . import diagnostics # noqa: F401 — registers diagnostics handler
22
22
  from . import clip_automation # noqa: F401 — registers clip automation handlers
23
+ from . import version_detect # noqa: F401 — version detection
23
24
 
24
25
 
25
26
  def create_instance(c_instance):
@@ -36,6 +37,12 @@ class LivePilot(ControlSurface):
36
37
  self._server.start()
37
38
  self.log_message("LivePilot v%s starting..." % __version__)
38
39
  self.show_message("LivePilot v%s starting..." % __version__)
40
+ v = version_detect.version_string()
41
+ self.log_message("LivePilot detected Ableton Live %s" % v)
42
+ features = version_detect.get_api_features()
43
+ enabled = [k for k, flag in features.items() if flag]
44
+ if enabled:
45
+ self.log_message(" Enabled features: %s" % ", ".join(enabled))
39
46
 
40
47
  def disconnect(self):
41
48
  """Called by Ableton when the script is unloaded."""
@@ -169,6 +169,70 @@ def create_arrangement_clip(song, params):
169
169
  }
170
170
 
171
171
 
172
+ @register("create_native_arrangement_clip")
173
+ def create_native_arrangement_clip(song, params):
174
+ """Create an empty MIDI clip in arrangement using the native 12.1.10+ API.
175
+
176
+ Unlike create_arrangement_clip (which duplicates a session clip),
177
+ this creates a true native clip with full automation envelope support.
178
+
179
+ Required: track_index, start_time, length
180
+ Optional: name, color_index
181
+ """
182
+ from .version_detect import has_feature
183
+
184
+ if not has_feature("create_midi_clip_arrangement"):
185
+ raise RuntimeError(
186
+ "create_native_arrangement_clip requires Live 12.1.10+. "
187
+ "Use create_arrangement_clip (session clip duplication) instead."
188
+ )
189
+
190
+ track_index = int(params["track_index"])
191
+ start_time = float(params["start_time"])
192
+ length = float(params["length"])
193
+ if length <= 0:
194
+ raise ValueError("length must be > 0")
195
+ if start_time < 0:
196
+ raise ValueError("start_time must be >= 0")
197
+
198
+ track = get_track(song, track_index)
199
+ if not track.has_midi_input:
200
+ raise ValueError(
201
+ "Track %d is not a MIDI track — create_native_arrangement_clip "
202
+ "only works on MIDI tracks" % track_index
203
+ )
204
+
205
+ song.begin_undo_step()
206
+ try:
207
+ clip = track.create_midi_clip(start_time, length)
208
+
209
+ name = params.get("name")
210
+ if name:
211
+ clip.name = str(name)
212
+ color_index = params.get("color_index")
213
+ if color_index is not None:
214
+ clip.color_index = int(color_index)
215
+ finally:
216
+ song.end_undo_step()
217
+
218
+ # Find the clip index in arrangement_clips
219
+ clip_index = None
220
+ for i, c in enumerate(track.arrangement_clips):
221
+ if abs(c.start_time - start_time) < 0.01:
222
+ clip_index = i
223
+ break
224
+
225
+ return {
226
+ "track_index": track_index,
227
+ "clip_index": clip_index,
228
+ "start_time": start_time,
229
+ "length": length,
230
+ "name": clip.name,
231
+ "has_envelope_support": True,
232
+ "native": True,
233
+ }
234
+
235
+
172
236
  @register("add_arrangement_notes")
173
237
  def add_arrangement_notes(song, params):
174
238
  """Add MIDI notes to an arrangement clip (by index in arrangement_clips)."""
@@ -713,3 +777,53 @@ def back_to_arranger(song, params):
713
777
  """Switch playback from session clips back to the arrangement timeline."""
714
778
  song.back_to_arranger = True
715
779
  return {"back_to_arranger": True}
780
+
781
+
782
+ @register("force_arrangement")
783
+ def force_arrangement(song, params):
784
+ """Force ALL tracks to follow the arrangement timeline.
785
+
786
+ Stops all session clips, releases every track from session override,
787
+ sets back_to_arranger, and optionally jumps to a start position.
788
+
789
+ This is the atomic "play the arrangement from the top" command.
790
+ """
791
+ # 1. Stop playback
792
+ was_playing = song.is_playing
793
+ if was_playing:
794
+ song.stop_playing()
795
+
796
+ # 2. Stop playing clip slots individually to release session overrides
797
+ # (track.stop_all_clips() throws STATE_ERROR when tracks have no clips)
798
+ for track in list(song.tracks) + list(song.return_tracks):
799
+ try:
800
+ for slot in track.clip_slots:
801
+ if slot.has_clip and slot.is_playing:
802
+ slot.clip.stop()
803
+ except Exception:
804
+ pass
805
+
806
+ # 3. Global back-to-arranger
807
+ song.back_to_arranger = True
808
+
809
+ # 4. Jump to position (default: start)
810
+ beat_time = float(params.get("beat_time", 0))
811
+ song.current_song_time = max(0, beat_time)
812
+
813
+ # 5. Set loop if requested
814
+ if "loop_length" in params:
815
+ song.loop_start = float(params.get("loop_start", 0))
816
+ song.loop_length = float(params["loop_length"])
817
+ song.loop = True
818
+
819
+ # 6. Start playback if requested (default: yes)
820
+ play = params.get("play", True)
821
+ if play:
822
+ song.start_playing()
823
+
824
+ return {
825
+ "arrangement_active": True,
826
+ "position": song.current_song_time,
827
+ "is_playing": song.is_playing,
828
+ "loop": song.loop,
829
+ }
@@ -1,5 +1,5 @@
1
1
  """
2
- LivePilot - Browser domain handlers (5 commands).
2
+ LivePilot - Browser domain handlers (6 commands).
3
3
  """
4
4
 
5
5
  import Live
@@ -386,6 +386,61 @@ def load_browser_item(song, params):
386
386
  )
387
387
 
388
388
 
389
+ _SCAN_MAX_ITERATIONS = 100000
390
+
391
+
392
+ def _scan_recursive(item, results, depth, max_depth, max_per_category,
393
+ _counter=None):
394
+ """Recursively collect loadable browser items with iteration cap."""
395
+ if _counter is None:
396
+ _counter = [0]
397
+ if depth > max_depth or len(results) >= max_per_category:
398
+ return
399
+ for child in item.children:
400
+ _counter[0] += 1
401
+ if _counter[0] > _SCAN_MAX_ITERATIONS or len(results) >= max_per_category:
402
+ return
403
+ if child.is_loadable:
404
+ entry = {"name": child.name, "is_loadable": True}
405
+ try:
406
+ entry["uri"] = child.uri
407
+ except AttributeError:
408
+ entry["uri"] = None
409
+ results.append(entry)
410
+ if child.is_folder:
411
+ _scan_recursive(
412
+ child, results, depth + 1, max_depth, max_per_category,
413
+ _counter
414
+ )
415
+ if len(results) >= max_per_category:
416
+ return
417
+
418
+
419
+ @register("scan_browser_deep")
420
+ def scan_browser_deep(song, params):
421
+ """Walk the entire browser tree and return all loadable items by category.
422
+
423
+ Parameters
424
+ ----------
425
+ max_per_category : int, optional
426
+ Maximum items to collect per top-level category (default 1000).
427
+ max_depth : int, optional
428
+ Maximum recursion depth into the browser tree (default 4).
429
+ """
430
+ max_per_category = int(params.get("max_per_category", 1000))
431
+ max_depth = int(params.get("max_depth", 4))
432
+ browser = _get_browser()
433
+ categories = _get_categories(browser)
434
+
435
+ result = {}
436
+ for cat_name, cat_item in categories.items():
437
+ items = []
438
+ _scan_recursive(cat_item, items, 0, max_depth, max_per_category)
439
+ result[cat_name] = items
440
+
441
+ return {"categories": result}
442
+
443
+
389
444
  @register("get_device_presets")
390
445
  def get_device_presets(song, params):
391
446
  """List available presets for a device type by searching the browser.