livepilot 1.12.2 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/README.md +7 -7
  3. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  4. package/m4l_device/livepilot_bridge.js +1 -1
  5. package/mcp_server/__init__.py +1 -1
  6. package/mcp_server/branches/__init__.py +34 -0
  7. package/mcp_server/branches/types.py +286 -0
  8. package/mcp_server/composer/__init__.py +10 -1
  9. package/mcp_server/composer/branch_producer.py +349 -0
  10. package/mcp_server/composer/tools.py +58 -1
  11. package/mcp_server/evaluation/policy.py +227 -2
  12. package/mcp_server/experiment/engine.py +47 -11
  13. package/mcp_server/experiment/models.py +112 -8
  14. package/mcp_server/experiment/tools.py +502 -38
  15. package/mcp_server/memory/taste_graph.py +84 -11
  16. package/mcp_server/persistence/taste_store.py +21 -5
  17. package/mcp_server/runtime/session_kernel.py +46 -0
  18. package/mcp_server/runtime/tools.py +29 -3
  19. package/mcp_server/server.py +1 -0
  20. package/mcp_server/synthesis_brain/__init__.py +53 -0
  21. package/mcp_server/synthesis_brain/adapters/__init__.py +34 -0
  22. package/mcp_server/synthesis_brain/adapters/analog.py +273 -0
  23. package/mcp_server/synthesis_brain/adapters/base.py +86 -0
  24. package/mcp_server/synthesis_brain/adapters/drift.py +271 -0
  25. package/mcp_server/synthesis_brain/adapters/meld.py +261 -0
  26. package/mcp_server/synthesis_brain/adapters/operator.py +292 -0
  27. package/mcp_server/synthesis_brain/adapters/wavetable.py +364 -0
  28. package/mcp_server/synthesis_brain/engine.py +91 -0
  29. package/mcp_server/synthesis_brain/models.py +121 -0
  30. package/mcp_server/synthesis_brain/timbre.py +194 -0
  31. package/mcp_server/synthesis_brain/tools.py +231 -0
  32. package/mcp_server/tools/_conductor.py +144 -0
  33. package/mcp_server/wonder_mode/engine.py +324 -0
  34. package/mcp_server/wonder_mode/tools.py +153 -1
  35. package/package.json +2 -2
  36. package/remote_script/LivePilot/__init__.py +1 -1
  37. package/server.json +3 -3
@@ -3,6 +3,13 @@
3
3
  Generates contextually different creative variants ranked by
4
4
  taste, identity, and coherence. Each variant is built from a
5
5
  real semantic move matched to the request.
6
+
7
+ PR6 adds a branch-native assembly path (generate_branch_seeds) that emits
8
+ BranchSeed objects from multiple producers — semantic_move, technique
9
+ memory, sacred-element inversion, and corpus-hint freeform seeds. The
10
+ existing generate_wonder_variants path is untouched; callers that speak
11
+ BranchSeed can consume the new function, callers that speak "variants"
12
+ keep the old one.
6
13
  """
7
14
 
8
15
  from __future__ import annotations
@@ -13,6 +20,8 @@ import logging
13
20
  import math
14
21
  from typing import Optional
15
22
 
23
+ from ..branches import BranchSeed, seed_from_move_id, freeform_seed
24
+
16
25
  logger = logging.getLogger(__name__)
17
26
 
18
27
 
@@ -626,3 +635,318 @@ def _wonder_id(request_text: str, kernel_id: str) -> str:
626
635
  """Deterministic variant ID prefix — no timestamp."""
627
636
  seed = json.dumps({"r": request_text, "k": kernel_id}, sort_keys=True)
628
637
  return "wm_" + hashlib.sha256(seed.encode()).hexdigest()[:10]
638
+
639
+
640
+ # ── PR6 — branch-native seed assembly ────────────────────────────────────
641
+
642
+
643
+ def _stable_seed_short_id(prefix: str, key: str) -> str:
644
+ """Deterministic short id for a seed, no timestamp."""
645
+ h = hashlib.sha256(f"{prefix}:{key}".encode()).hexdigest()[:10]
646
+ return f"{prefix}_{h}"
647
+
648
+
649
+ _MOVE_NOVELTY_BY_INDEX = ("safe", "strong", "unexpected")
650
+
651
+
652
+ def generate_branch_seeds(
653
+ request_text: str,
654
+ kernel: Optional[dict] = None,
655
+ song_brain: Optional[dict] = None,
656
+ active_constraints: object = None,
657
+ taste_graph: object = None,
658
+ max_seeds: int = 3,
659
+ ) -> list[BranchSeed]:
660
+ """Assemble BranchSeeds from multiple creative sources.
661
+
662
+ Branch-native companion to ``generate_wonder_variants``. Instead of
663
+ returning pre-built variant dicts tied to moves, emits BranchSeed
664
+ objects that can be fed to ``create_experiment_from_seeds`` or
665
+ ``create_experiment(seeds=[...])`` directly.
666
+
667
+ Sources (assembled in this order until max_seeds is reached):
668
+
669
+ 1. ``semantic_move`` — one seed per distinct move discovered by
670
+ ``discover_moves`` + ``select_distinct_variants``. Novelty tier
671
+ is assigned positionally: first move = safe, second = strong,
672
+ third = unexpected.
673
+
674
+ 2. ``technique`` — freeform seeds built from kernel.session_memory
675
+ entries whose category is "technique" or "success". Low-novelty
676
+ by design (known-good).
677
+
678
+ 3. sacred-element inversion — freeform seeds that deliberately
679
+ contrast a sacred element from ``song_brain.sacred_elements``.
680
+ Only emitted when kernel.freshness >= 0.5. High-novelty,
681
+ high-risk. These seeds are typically analytical until a
682
+ producer (Wonder itself or synthesis_brain) compiles them.
683
+
684
+ 4. corpus hints — freeform seeds built from ``_get_corpus_hints``.
685
+ Medium novelty, grounded in the corpus knowledge base.
686
+
687
+ Distinctness rules:
688
+ - At most one seed with each exact hypothesis (case-insensitive)
689
+ - semantic_move seeds already pass through select_distinct_variants
690
+ so their (family, plan_shape) distinctness is preserved
691
+
692
+ When kernel is None, the function still works — it just skips the
693
+ kernel-driven sources (technique memory, freshness-gated inversion).
694
+
695
+ Does NOT include synthesis or composer seeds — those emit
696
+ (seed, compiled_plan) pairs and must be handled by callers that
697
+ can thread plans through. Use generate_branch_seeds_and_plans() for
698
+ the full multi-producer assembly.
699
+ """
700
+ kernel = kernel or {}
701
+ song_brain = song_brain or {}
702
+ seeds: list[BranchSeed] = []
703
+ used_hypotheses: set[str] = set()
704
+ freshness = float(kernel.get("freshness", 0.5) or 0.5)
705
+
706
+ # ── 1. semantic_move seeds ────────────────────────────────────────
707
+ moves = discover_moves(
708
+ request_text,
709
+ taste_graph=taste_graph,
710
+ active_constraints=active_constraints,
711
+ )
712
+ distinct_moves = select_distinct_variants(moves)
713
+
714
+ for i, move in enumerate(distinct_moves):
715
+ if len(seeds) >= max_seeds:
716
+ return seeds
717
+ label = _MOVE_NOVELTY_BY_INDEX[i] if i < len(_MOVE_NOVELTY_BY_INDEX) else "strong"
718
+ hypothesis = move.get("intent") or f"Apply {move.get('move_id', '')}"
719
+ seed = seed_from_move_id(
720
+ move_id=move.get("move_id", ""),
721
+ hypothesis=hypothesis,
722
+ novelty_label=label,
723
+ risk_label=move.get("risk_level", "low"),
724
+ distinctness_reason=_explain_distinctness(move, distinct_moves, i),
725
+ )
726
+ seeds.append(seed)
727
+ used_hypotheses.add(hypothesis.lower())
728
+
729
+ # ── 2. technique seeds from session memory ────────────────────────
730
+ session_mem = kernel.get("session_memory") or []
731
+ for mem in session_mem:
732
+ if len(seeds) >= max_seeds:
733
+ return seeds
734
+ if mem.get("category") not in ("technique", "success"):
735
+ continue
736
+ content = (mem.get("content") or "").strip()
737
+ if not content:
738
+ continue
739
+ hyp = f"Replay: {content[:100]}"
740
+ if hyp.lower() in used_hypotheses:
741
+ continue
742
+ seed = freeform_seed(
743
+ seed_id=_stable_seed_short_id("tech", content),
744
+ hypothesis=hyp,
745
+ source="technique",
746
+ novelty_label="safe",
747
+ risk_label="low",
748
+ distinctness_reason="recalled from session memory — known to work",
749
+ )
750
+ seeds.append(seed)
751
+ used_hypotheses.add(hyp.lower())
752
+
753
+ # ── 3. sacred-element inversion (freshness-gated) ────────────────
754
+ if freshness >= 0.5:
755
+ sacred = song_brain.get("sacred_elements") or []
756
+ for s in sacred[:2]:
757
+ if len(seeds) >= max_seeds:
758
+ return seeds
759
+ elem_type = s.get("element_type", "element")
760
+ desc = s.get("description") or elem_type
761
+ hyp = f"What if we invert {desc}?"
762
+ if hyp.lower() in used_hypotheses:
763
+ continue
764
+ # Protect every OTHER sacred element — the point is to contrast
765
+ # one of them deliberately.
766
+ other_protected = [
767
+ other.get("element_type", "")
768
+ for other in sacred
769
+ if other.get("element_type") != elem_type
770
+ ]
771
+ seed = freeform_seed(
772
+ seed_id=_stable_seed_short_id("invert", elem_type),
773
+ hypothesis=hyp,
774
+ source="freeform",
775
+ novelty_label="unexpected",
776
+ risk_label="high",
777
+ protected_qualities=[p for p in other_protected if p],
778
+ distinctness_reason=(
779
+ f"deliberately contrasts the '{desc}' that baseline "
780
+ f"branches preserve"
781
+ ),
782
+ )
783
+ seeds.append(seed)
784
+ used_hypotheses.add(hyp.lower())
785
+
786
+ # ── 4. corpus-hint seeds ──────────────────────────────────────────
787
+ corpus_hints = _get_corpus_hints(request_text, song_brain.get("diagnosis"))
788
+ if corpus_hints:
789
+ for kind, hint in corpus_hints.items():
790
+ if len(seeds) >= max_seeds:
791
+ return seeds
792
+ if not isinstance(hint, dict):
793
+ continue
794
+ if kind == "emotional_recipe":
795
+ hyp = (
796
+ f"Apply {hint.get('emotion', 'emotional')} recipe "
797
+ f"({hint.get('technique_count', 0)} techniques)"
798
+ )
799
+ elif kind == "genre_chain":
800
+ devices = hint.get("devices", []) or []
801
+ hyp = (
802
+ f"Build {hint.get('genre', 'genre')} chain: "
803
+ f"{', '.join(devices[:3])}"
804
+ )
805
+ elif kind == "physical_model":
806
+ devices = hint.get("devices", []) or []
807
+ hyp = (
808
+ f"Model {hint.get('material', 'material')} via "
809
+ f"{', '.join(devices[:3])}"
810
+ )
811
+ else:
812
+ continue
813
+
814
+ if hyp.lower() in used_hypotheses:
815
+ continue
816
+ seed = freeform_seed(
817
+ seed_id=_stable_seed_short_id("corpus", f"{kind}:{hyp}"),
818
+ hypothesis=hyp,
819
+ source="freeform",
820
+ novelty_label="strong",
821
+ risk_label="medium",
822
+ distinctness_reason=f"corpus-grounded {kind.replace('_', ' ')}",
823
+ )
824
+ seeds.append(seed)
825
+ used_hypotheses.add(hyp.lower())
826
+
827
+ return seeds
828
+
829
+
830
+ def generate_branch_seeds_and_plans(
831
+ request_text: str,
832
+ kernel: Optional[dict] = None,
833
+ song_brain: Optional[dict] = None,
834
+ active_constraints: object = None,
835
+ taste_graph: object = None,
836
+ max_seeds: int = 3,
837
+ synth_profiles: Optional[list] = None,
838
+ composer_request: Optional[str] = None,
839
+ composer_count: int = 2,
840
+ ) -> tuple[list[BranchSeed], dict[str, dict]]:
841
+ """Full multi-producer branch assembly with pre-compiled plans.
842
+
843
+ Extends ``generate_branch_seeds`` to reach the synthesis and composer
844
+ producers — both of which emit ``(seed, compiled_plan)`` pairs that
845
+ cannot be expressed through the seed-only return type of the base
846
+ function. Returns a tuple:
847
+
848
+ (seeds, compiled_plans_by_seed_id)
849
+
850
+ where ``compiled_plans_by_seed_id`` maps seed_id → plan dict. Seeds
851
+ from producers that don't compile ship with their seed_id absent
852
+ from the dict (e.g. analytical seeds, corpus hints).
853
+
854
+ Additional inputs beyond the base function:
855
+
856
+ synth_profiles: list of :class:`SynthProfile` objects (from
857
+ ``synthesis_brain.analyze_synth_patch``). When non-empty, each
858
+ profile is passed to ``propose_synth_branches`` and the returned
859
+ pairs are merged into the output. Typically the caller fetches
860
+ device parameters via ``ableton.send_command('get_device_parameters')``
861
+ and builds a profile per device before calling this function.
862
+
863
+ composer_request: natural-language composition prompt. When set,
864
+ ``propose_composer_branches`` is invoked with it and the emitted
865
+ pairs are merged. For composition-shaped requests, pass
866
+ ``request_text`` here.
867
+
868
+ composer_count: max composer branches to emit (default 2).
869
+
870
+ Ordering matches generate_branch_seeds where sources overlap:
871
+ semantic_move → technique → synthesis → sacred-inversion → composer
872
+ → corpus hints. max_seeds still caps total output.
873
+ """
874
+ # Base seeds (semantic_move, technique, sacred-inversion, corpus)
875
+ base_seeds = generate_branch_seeds(
876
+ request_text=request_text,
877
+ kernel=kernel,
878
+ song_brain=song_brain,
879
+ active_constraints=active_constraints,
880
+ taste_graph=taste_graph,
881
+ max_seeds=max_seeds,
882
+ )
883
+
884
+ # Copy the base seeds so we can interleave producer seeds without
885
+ # mutating the cached list (if the caller happens to share it).
886
+ seeds: list[BranchSeed] = list(base_seeds)
887
+ plans_by_seed: dict[str, dict] = {}
888
+ used_hypotheses = {s.hypothesis.lower() for s in seeds}
889
+ budget_remaining = max(0, max_seeds - len(seeds))
890
+
891
+ # ── synthesis producer ────────────────────────────────────────────
892
+ if budget_remaining > 0 and synth_profiles:
893
+ try:
894
+ from ..synthesis_brain import propose_synth_branches
895
+ except ImportError as exc:
896
+ logger.warning("synthesis_brain unavailable: %s", exc)
897
+ propose_synth_branches = None
898
+
899
+ if propose_synth_branches is not None:
900
+ for profile in synth_profiles:
901
+ if budget_remaining <= 0:
902
+ break
903
+ try:
904
+ pairs = propose_synth_branches(profile, kernel=kernel)
905
+ except Exception as exc:
906
+ logger.warning(
907
+ "propose_synth_branches failed for %s: %s",
908
+ getattr(profile, "device_name", "?"),
909
+ exc,
910
+ )
911
+ continue
912
+ for seed, plan in pairs:
913
+ if budget_remaining <= 0:
914
+ break
915
+ if seed.hypothesis.lower() in used_hypotheses:
916
+ continue
917
+ seeds.append(seed)
918
+ plans_by_seed[seed.seed_id] = plan
919
+ used_hypotheses.add(seed.hypothesis.lower())
920
+ budget_remaining -= 1
921
+
922
+ # ── composer producer ────────────────────────────────────────────
923
+ if budget_remaining > 0 and composer_request:
924
+ try:
925
+ from ..composer import propose_composer_branches
926
+ except ImportError as exc:
927
+ logger.warning("composer branch producer unavailable: %s", exc)
928
+ propose_composer_branches = None
929
+
930
+ if propose_composer_branches is not None:
931
+ try:
932
+ comp_pairs = propose_composer_branches(
933
+ request_text=composer_request,
934
+ kernel=kernel,
935
+ count=min(composer_count, budget_remaining),
936
+ )
937
+ except Exception as exc:
938
+ logger.warning("propose_composer_branches failed: %s", exc)
939
+ comp_pairs = []
940
+
941
+ for seed, plan in comp_pairs:
942
+ if budget_remaining <= 0:
943
+ break
944
+ if seed.hypothesis.lower() in used_hypotheses:
945
+ continue
946
+ seeds.append(seed)
947
+ if plan: # composer may return {} for analytical-only branches
948
+ plans_by_seed[seed.seed_id] = plan
949
+ used_hypotheses.add(seed.hypothesis.lower())
950
+ budget_remaining -= 1
951
+
952
+ return seeds, plans_by_seed
@@ -17,6 +17,96 @@ from . import engine
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
19
 
20
+ def _build_synth_profiles_for_wonder(ctx, request_text: str, diagnosis: dict) -> list:
21
+ """Build SynthProfile objects for every track holding a native synth.
22
+
23
+ Wires the synthesis_brain producer into enter_wonder_mode's runtime
24
+ flow — without this helper, ``propose_synth_branches`` is registered
25
+ but unreachable from the MCP surface.
26
+
27
+ Returns a list of SynthProfile objects (possibly empty). All errors
28
+ are swallowed and logged — missing devices, disconnected Ableton,
29
+ unsupported devices all land as "no synth branches" rather than
30
+ failing the whole wonder session.
31
+ """
32
+ try:
33
+ from ..synthesis_brain import analyze_synth_patch, supported_devices
34
+ except ImportError as exc:
35
+ logger.debug("synthesis_brain not importable: %s", exc)
36
+ return []
37
+
38
+ ableton = ctx.lifespan_context.get("ableton")
39
+ if ableton is None:
40
+ return []
41
+
42
+ try:
43
+ session = ableton.send_command("get_session_info", {})
44
+ except Exception as exc:
45
+ logger.debug("session fetch for synth profiles failed: %s", exc)
46
+ return []
47
+ if not isinstance(session, dict) or "error" in session:
48
+ return []
49
+
50
+ native_names = set(supported_devices())
51
+ profiles: list = []
52
+
53
+ tracks = session.get("tracks") or []
54
+ for track in tracks:
55
+ track_index = track.get("index")
56
+ devices = track.get("devices") or []
57
+ if track_index is None or not devices:
58
+ continue
59
+ for dev in devices:
60
+ dev_name = dev.get("name") or dev.get("class_name") or ""
61
+ if dev_name not in native_names:
62
+ continue
63
+ dev_index = dev.get("index")
64
+ if dev_index is None:
65
+ continue
66
+ try:
67
+ params_result = ableton.send_command(
68
+ "get_device_parameters",
69
+ {"track_index": track_index, "device_index": dev_index},
70
+ )
71
+ except Exception as exc:
72
+ logger.debug(
73
+ "get_device_parameters failed for track=%s device=%s: %s",
74
+ track_index, dev_index, exc,
75
+ )
76
+ continue
77
+ if not isinstance(params_result, dict) or "error" in params_result:
78
+ continue
79
+
80
+ parameter_state = {}
81
+ display_values = {}
82
+ for p in params_result.get("parameters", []) or []:
83
+ name = p.get("name")
84
+ if name is None:
85
+ continue
86
+ parameter_state[name] = p.get("value")
87
+ if "value_string" in p:
88
+ display_values[name] = p["value_string"]
89
+
90
+ try:
91
+ profile = analyze_synth_patch(
92
+ device_name=dev_name,
93
+ track_index=track_index,
94
+ device_index=dev_index,
95
+ parameter_state=parameter_state,
96
+ display_values=display_values,
97
+ role_hint=track.get("name", ""),
98
+ )
99
+ profiles.append(profile)
100
+ except Exception as exc:
101
+ logger.warning(
102
+ "analyze_synth_patch failed for %s on track %s: %s",
103
+ dev_name, track_index, exc,
104
+ )
105
+ continue
106
+
107
+ return profiles
108
+
109
+
20
110
  def _get_song_brain_dict() -> dict:
21
111
  try:
22
112
  from ..song_brain.tools import _current_brain
@@ -164,7 +254,7 @@ def enter_wonder_mode(
164
254
  except Exception as exc:
165
255
  logger.warning("session_info fetch for kernel failed: %s", exc)
166
256
 
167
- # 2. Generate variants
257
+ # 2. Generate variants (legacy path)
168
258
  result = engine.generate_wonder_variants(
169
259
  request_text=request_text,
170
260
  diagnosis=diag_dict,
@@ -176,6 +266,58 @@ def enter_wonder_mode(
176
266
  sample_context=sample_context,
177
267
  )
178
268
 
269
+ # 2b. PR6 — also emit BranchSeeds for the branch-native experiment flow.
270
+ # Pull session_memory from the conversation lifespan when available so
271
+ # technique seeds can be sourced. Kernel state here is a lightweight
272
+ # dict — enter_wonder_mode doesn't always have a full SessionKernel.
273
+ branch_kernel: dict = {}
274
+ try:
275
+ from ..memory.session_memory import SessionMemoryStore
276
+ mem_store = ctx.lifespan_context.setdefault("session_memory", SessionMemoryStore())
277
+ branch_kernel["session_memory"] = [
278
+ entry.to_dict() for entry in mem_store.get_recent(limit=10)
279
+ ]
280
+ except Exception as exc:
281
+ logger.debug("session_memory fetch for seeds failed: %s", exc)
282
+ # Freshness defaults to 0.5 — enter_wonder_mode is inherently exploratory,
283
+ # so lean slightly toward surprise.
284
+ branch_kernel["freshness"] = 0.65
285
+
286
+ # Build SynthProfiles for tracks mentioned in the request or for every
287
+ # track that holds a native synth. Lets propose_synth_branches reach
288
+ # the runtime — without this the producer is dark despite being
289
+ # registered in the conductor.
290
+ synth_profiles = _build_synth_profiles_for_wonder(ctx, request_text, diag_dict)
291
+
292
+ # Composer branches fire when the base conductor routes to
293
+ # composition — otherwise we'd be emitting composition scaffolding
294
+ # for every mix/transition request, which is noise.
295
+ composer_request: str = ""
296
+ try:
297
+ from ..tools._conductor import classify_request
298
+ base_plan = classify_request(request_text)
299
+ if base_plan.routes and base_plan.routes[0].engine == "composition":
300
+ composer_request = request_text
301
+ except Exception as exc:
302
+ logger.debug("composer routing check failed: %s", exc)
303
+
304
+ try:
305
+ branch_seeds, compiled_plans_by_seed = engine.generate_branch_seeds_and_plans(
306
+ request_text=request_text,
307
+ kernel=branch_kernel,
308
+ song_brain=song_brain,
309
+ active_constraints=active_constraints,
310
+ taste_graph=taste_graph,
311
+ synth_profiles=synth_profiles,
312
+ composer_request=composer_request or None,
313
+ )
314
+ branch_seeds_dicts = [s.to_dict() for s in branch_seeds]
315
+ except Exception as exc:
316
+ # Seed assembly is additive — never let it break wonder mode.
317
+ logger.warning("generate_branch_seeds_and_plans failed: %s", exc)
318
+ branch_seeds_dicts = []
319
+ compiled_plans_by_seed = {}
320
+
179
321
  # 3. Create WonderSession (unique per invocation, not deterministic)
180
322
  import hashlib, time
181
323
  _seed = f"{request_text}:{kernel_id}:{time.time()}"
@@ -217,6 +359,16 @@ def enter_wonder_mode(
217
359
  "recommended": result.get("recommended", ""),
218
360
  "variant_count_actual": result.get("variant_count_actual", 0),
219
361
  "degraded_reason": ws.degraded_reason,
362
+ # Branch-native seeds from six producers (semantic_move, technique,
363
+ # synthesis, sacred-inversion, composer, corpus). Feed directly to
364
+ # create_experiment(seeds=[...], compiled_plans=[...]) — align the
365
+ # compiled_plans list with branch_seeds by matching seed_ids.
366
+ "branch_seeds": branch_seeds_dicts,
367
+ # seed_id → pre-compiled plan dict. Only synthesis and composer
368
+ # seeds currently populate this — other sources either defer
369
+ # compilation to run_experiment (semantic_move) or stay analytical
370
+ # (technique reuse, sacred-inversion, corpus hints).
371
+ "compiled_plans_by_seed_id": compiled_plans_by_seed,
220
372
  "mode": "wonder",
221
373
  }
222
374
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.12.2",
3
+ "version": "1.14.0",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "Agentic production system for Ableton Live 12 — 398 tools, 51 domains. Device atlas (1305 devices), sample engine (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, creative intelligence (12 engines)",
5
+ "description": "Agentic production system for Ableton Live 12 — 402 tools, 52 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
7
  "license": "BSL-1.1",
8
8
  "type": "commonjs",
@@ -5,7 +5,7 @@ Entry point for the ControlSurface. Ableton calls create_instance(c_instance)
5
5
  when this script is selected in Preferences > Link, Tempo & MIDI.
6
6
  """
7
7
 
8
- __version__ = "1.12.2"
8
+ __version__ = "1.14.0"
9
9
 
10
10
  from _Framework.ControlSurface import ControlSurface
11
11
  from . import router
package/server.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.dreamrec/livepilot",
4
- "description": "398-tool agentic MCP production system for Ableton Live 12 — device atlas, sample engine, composer",
4
+ "description": "402-tool agentic MCP production system for Ableton Live 12 — device atlas, sample engine, composer",
5
5
  "repository": {
6
6
  "url": "https://github.com/dreamrec/LivePilot",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.12.2",
9
+ "version": "1.14.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "livepilot",
14
- "version": "1.12.2",
14
+ "version": "1.14.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }