livepilot 1.24.0 → 1.25.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.
@@ -1,27 +1,51 @@
1
1
  """KnowledgePack — assembles the vocabulary fields each compose mode's brief carries.
2
2
 
3
- For full mode: rich (event_lexicon, genre_context, artist_context, atlas_candidates, manual_snippets).
4
- For fast mode: subset (no event_lexicon — fast mode is loop-sketch scope, doesn't design form).
3
+ For full mode: rich (event_lexicon, genre_context, artist_context,
4
+ atlas_anchors, atlas_candidates_per_role, manual_snippets).
5
+ For fast mode: subset (no event_lexicon — fast mode is loop-sketch scope).
5
6
  For develop mode: subset overlapping with full.
7
+
8
+ v1.25 adds `atlas_anchors` — cohort + role-anchored URIs from
9
+ atlas_pack_aware_compose, populated when atlas + brief_text are provided.
10
+ The agent uses `atlas_explore` / `atlas_audition` / `atlas_substitute`
11
+ mid-design to dig past anchors when needed (hybrid knowledge surface).
6
12
  """
7
13
 
8
14
  from __future__ import annotations
9
- from typing import Any
15
+
16
+ import logging
17
+ from dataclasses import asdict
18
+ from typing import Any, Optional
10
19
 
11
20
  from .event_lexicon import EVENT_LEXICON, get_event_lexicon
12
21
  from .genre_loader import load_genre_context
13
22
  from .artist_loader import load_artist_context
14
23
 
24
+ logger = logging.getLogger(__name__)
25
+
15
26
 
16
27
  class KnowledgePack:
17
28
  """Central knowledge assembly for compose-mode briefs."""
18
29
 
19
- def build(self, intent: dict, mode: str = "full") -> dict:
30
+ def build(
31
+ self,
32
+ intent: dict,
33
+ mode: str = "full",
34
+ *,
35
+ atlas: Any = None,
36
+ ableton: Any = None,
37
+ ctx: Any = None,
38
+ brief_text: str = "",
39
+ ) -> dict:
20
40
  """Build the knowledge fields for a brief.
21
41
 
22
42
  intent: parsed CompositionIntent dict with at least 'genre', possibly 'sub_genre'.
23
43
  May also carry 'artists' (list of producer names).
24
44
  mode: 'fast' | 'full' | 'develop'
45
+ atlas: AtlasManager instance (optional — when None, atlas_anchors is None)
46
+ ableton: ableton client (optional — reserved for v1.25.x browser fallback)
47
+ ctx: lifespan context (optional — reserved for taste_profile / recent_uris)
48
+ brief_text: the original prompt string (required for atlas_anchors)
25
49
 
26
50
  Returns a dict — pass directly to brief.knowledge OR spread into brief top-level.
27
51
  """
@@ -33,11 +57,12 @@ class KnowledgePack:
33
57
  artist_names = intent.get("artists") or []
34
58
  artist_ctx = load_artist_context(artist_names)
35
59
 
36
- knowledge = {
60
+ knowledge: dict[str, Any] = {
37
61
  "genre_context": genre_ctx,
38
62
  "artist_context": artist_ctx,
39
- "atlas_candidates_per_role": {}, # populated by brief_builder via existing get_role_candidates
40
- "manual_snippets": {}, # populated by brief_builder per likely device
63
+ "atlas_candidates_per_role": {}, # legacy field empty in v1.25 (replaced by atlas_anchors)
64
+ "atlas_anchors": None, # populated below for full mode when atlas available
65
+ "manual_snippets": {},
41
66
  }
42
67
 
43
68
  # Event lexicon — full mode only (loop sketch doesn't design form)
@@ -46,4 +71,70 @@ class KnowledgePack:
46
71
  else:
47
72
  knowledge["event_lexicon"] = []
48
73
 
74
+ # Atlas anchors — full mode only, requires atlas + brief_text. Best-effort:
75
+ # any failure path silently leaves anchors=None and the brief still works.
76
+ if mode == "full" and atlas is not None and brief_text:
77
+ try:
78
+ from .atlas_resolver import AtlasResolver
79
+ resolver = AtlasResolver(
80
+ atlas=atlas,
81
+ ableton=ableton,
82
+ taste_profile=_safe_get_taste_profile(ctx),
83
+ recent_uris=_safe_get_recent_uris(ctx),
84
+ )
85
+ mood = _extract_mood(intent)
86
+ anchors = resolver.resolve_anchors(
87
+ brief_text=brief_text,
88
+ genre=genre,
89
+ mood=mood,
90
+ artist_refs=artist_names,
91
+ )
92
+ knowledge["atlas_anchors"] = asdict(anchors)
93
+ except Exception as exc:
94
+ logger.debug("KnowledgePack.build: atlas_anchors unavailable: %s", exc)
95
+
49
96
  return knowledge
97
+
98
+
99
+ # ── Helpers ─────────────────────────────────────────────────────────
100
+
101
+
102
+ def _extract_mood(intent: dict) -> str:
103
+ """Derive a mood string from the parsed intent.
104
+
105
+ Combines mood + descriptors fields when present. Falls back to "" so
106
+ the resolver's mood-overlap boost simply doesn't fire (rather than
107
+ matching against junk).
108
+ """
109
+ parts: list[str] = []
110
+ for key in ("mood", "descriptors", "modifiers"):
111
+ val = intent.get(key)
112
+ if isinstance(val, str) and val:
113
+ parts.append(val)
114
+ elif isinstance(val, (list, tuple)):
115
+ parts.extend(str(v) for v in val if v)
116
+ return " ".join(parts).strip()
117
+
118
+
119
+ def _safe_get_taste_profile(ctx: Any) -> Optional[dict]:
120
+ """Best-effort taste profile fetch. Returns None on any failure."""
121
+ if ctx is None:
122
+ return None
123
+ try:
124
+ # The taste graph tools live under mcp_server/tools/agent_os.py;
125
+ # importing here would create a cycle, so leave as None for now.
126
+ # v1.25.x will wire in get_taste_profile() once the cycle is broken.
127
+ return None
128
+ except Exception:
129
+ return None
130
+
131
+
132
+ def _safe_get_recent_uris(ctx: Any) -> Optional[set[str]]:
133
+ """Best-effort recent-URIs fetch (§7 #2 anti-repeat). Returns None on failure."""
134
+ if ctx is None:
135
+ return None
136
+ try:
137
+ # Same cycle concern as _safe_get_taste_profile — defer to v1.25.x.
138
+ return None
139
+ except Exception:
140
+ return None
@@ -46,12 +46,62 @@ _DESIGN_TARGETS = (
46
46
  "the song. Use the event_lexicon as a vocabulary of named structural "
47
47
  "moves to schedule at chosen phrase boundaries. For niche style references "
48
48
  "in research_hooks, run WebSearch to ground your form choices in the "
49
- "actual conventions of that subgenre. Submit your design as a plan to "
50
- "compose_full_apply with: per-track variant clips at chosen scene slots, "
51
- "per-section arrangement_clip placements referencing those variants, "
52
- "and structural events scheduled at phrase boundaries. The form is YOUR "
53
- "creative product vocabularies tell you what techno or BoC sound like, "
54
- "they do not tell you the bar count of an intro."
49
+ "actual conventions of that subgenre.\n\n"
50
+ "INSTRUMENT SELECTION (v1.25 hybrid knowledge surface MANDATED FOUR-SOURCE SEARCH):\n"
51
+ "The brief's `atlas_anchors` is ONE source. Before committing any role pick "
52
+ "you MUST also query the other three sources below. Factory-atlas-only picks "
53
+ "have repeatedly missed canonical user-curated instruments (e.g., the 808 Trap "
54
+ "Selector Rack from Trap Drums by Sound Oracle pack lives in the packs overlay, "
55
+ "not the factory tag index). Always union BEFORE deciding.\n\n"
56
+ "Source 1 — Factory atlas (already surfaced in atlas_anchors). Three tools:\n"
57
+ " • atlas_audition(uri) — full sidecar dump for a chosen URI: character "
58
+ "tags, signature_techniques, producer-curated macro values, related demos. "
59
+ "Call this BEFORE committing to a candidate when its tags alone aren't "
60
+ "enough to know if it fits the section.\n"
61
+ " • atlas_explore(role, mood, genre, artists?) — refined per-role query "
62
+ "when an anchor doesn't fit the section's purpose, or when you need siblings "
63
+ "of a role pick. Returns 3-5 ranked candidates with reasoning trails.\n"
64
+ " • atlas_substitute(current_uri, anti_tag) — anti-tag-driven swap to use "
65
+ "AFTER analyze_sound_design or analyze_mix flags an issue (\"too bright\", "
66
+ "\"too aggressive\", \"too sparse\", \"muddy\", \"static\", \"generic\"). "
67
+ "Returns 3 alternatives that explicitly avoid the unwanted property.\n\n"
68
+ "Source 2 — User corpus (mandatory union — ~/.livepilot/atlas-overlays/):\n"
69
+ " • extension_atlas_search(query=\"<role>\", limit=10) — searches all four "
70
+ "overlay namespaces: `packs` (Ableton factory packs with hidden_gems, "
71
+ "notable_presets, signature_workflows fields), `m4l-devices` (curated M4L "
72
+ "device knowledge), `user.*` (your scanned .amxd / plugin / preset library), "
73
+ "`elektron` (hardware-mirror chains). Producer-curated rack instruments "
74
+ "(808 Trap Selector Rack, Harmonic Drone Generator, etc.) live here, NOT "
75
+ "in the factory tag index. Always run this query alongside atlas_explore.\n"
76
+ " • extension_atlas_search(query=\"<role-or-aesthetic>\", entity_type=\"demo_project\") — "
77
+ "GROUND-TRUTH ROLE→URI MAPPING. The packs namespace contains 100+ analyzed "
78
+ "demo .als project parses; each carries actual track-by-track instrument "
79
+ "URIs proven on real Ableton-shipped demos. For 808 bass, query "
80
+ "`demo_project` to find which pack-included .als demos use 808 bass and "
81
+ "what URI they loaded — the highest-confidence source for any role.\n"
82
+ " • extension_atlas_get(namespace, entity_id) — full body of a chosen entry "
83
+ "including hidden_gems and signature_workflows fields the search summary trims.\n\n"
84
+ "Source 3 — Anthropic Ableton Knowledge MCP (mcp__Ableton_Knowledge__*):\n"
85
+ " • search_transcripts(query) — Ableton's official tutorial video transcripts; "
86
+ "ground-truth pedagogy on how producers actually use the device.\n"
87
+ " • search_live_manual(query) — live manual snippets for any device or feature.\n"
88
+ " • search_knowledge_base(query) — broader Ableton knowledge base.\n"
89
+ " • search_videos(query) — official tutorial video metadata.\n"
90
+ " Run these for the ROLE term (e.g., \"808 bass\", \"sidechain compression\") "
91
+ "and for any artist/genre reference in the prompt to ground your design choices.\n\n"
92
+ "FOUR-SOURCE SEARCH PROTOCOL per role:\n"
93
+ " 1. Read atlas_anchors[role] (Source 1 starting point).\n"
94
+ " 2. Call atlas_explore(role, mood, genre, artists) (Source 1 ranked alts).\n"
95
+ " 3. Call extension_atlas_search(query=role) and extension_atlas_search(query=role, "
96
+ "entity_type=\"demo_project\") (Source 2 user corpus + ground-truth demos).\n"
97
+ " 4. Call mcp__Ableton_Knowledge__search_transcripts(query=role) for context (Source 3).\n"
98
+ " 5. Union results, score against brief's mood/aesthetic, then commit.\n\n"
99
+ "The framework's job was to surface the corpus. The picks are yours. "
100
+ "Submit your design as a plan to compose_full_apply with: per-track variant "
101
+ "clips at chosen scene slots, per-section arrangement_clip placements "
102
+ "referencing those variants, and structural events scheduled at phrase "
103
+ "boundaries. The form is YOUR creative product — vocabularies tell you "
104
+ "what techno or BoC sound like, they do not tell you the bar count of an intro."
55
105
  )
56
106
 
57
107
 
@@ -122,11 +172,21 @@ def build_full_brief(
122
172
  artist_refs = extract_artist_refs(prompt or "")
123
173
  research_hooks = detect_research_hooks(prompt or "")
124
174
 
125
- # Build knowledge pack — populates genre_context, artist_context, event_lexicon
175
+ # Build knowledge pack — populates genre_context, artist_context, event_lexicon,
176
+ # AND v1.25 atlas_anchors when atlas + brief_text are available.
126
177
  if artist_refs:
127
178
  intent_dict["artists"] = artist_refs
128
179
  kp = KnowledgePack()
129
- knowledge = kp.build(intent_dict, mode="full")
180
+ atlas_obj = _safe_get_atlas(ctx)
181
+ ableton_obj = _safe_get_ableton(ctx)
182
+ knowledge = kp.build(
183
+ intent_dict,
184
+ mode="full",
185
+ atlas=atlas_obj,
186
+ ableton=ableton_obj,
187
+ ctx=ctx,
188
+ brief_text=prompt or "",
189
+ )
130
190
 
131
191
  return {
132
192
  "mode": "full",
@@ -137,8 +197,31 @@ def build_full_brief(
137
197
  "artist_context": knowledge["artist_context"],
138
198
  "event_lexicon": knowledge["event_lexicon"],
139
199
  "atlas_candidates_per_role": knowledge["atlas_candidates_per_role"],
200
+ "atlas_anchors": knowledge.get("atlas_anchors"),
140
201
  "manual_snippets": knowledge["manual_snippets"],
141
202
  "seed_state": seed_state,
142
203
  "research_hooks": research_hooks,
143
204
  "design_targets": _DESIGN_TARGETS,
144
205
  }
206
+
207
+
208
+ # ── Lifespan-context helpers ───────────────────────────────────────
209
+
210
+
211
+ def _safe_get_atlas(ctx: Any) -> Optional[Any]:
212
+ """Best-effort atlas fetch. Returns None on any failure."""
213
+ try:
214
+ from ...atlas import get_atlas
215
+ return get_atlas()
216
+ except Exception:
217
+ return None
218
+
219
+
220
+ def _safe_get_ableton(ctx: Any) -> Optional[Any]:
221
+ """Best-effort ableton-client fetch from lifespan_context. None on miss."""
222
+ try:
223
+ if ctx is not None and hasattr(ctx, "lifespan_context"):
224
+ return ctx.lifespan_context.get("ableton")
225
+ except Exception:
226
+ pass
227
+ return None
@@ -258,12 +258,21 @@ async def _simpler_post_load_hygiene(
258
258
  # Step 4: auto-detect drum root note from filename (BUG-2026-04-22#18).
259
259
  # Only applied for one-shots — warped loops keep Live's default root
260
260
  # because their root note is irrelevant at loop playback speeds.
261
+ #
262
+ # 2026-05-02 — fixed param name: was "Sample Pitch Coarse" (doesn't exist
263
+ # on OriginalSimpler — silently failed). Correct param is "Transpose"
264
+ # (semitone offset from C3=60). Convert detected drum root → Transpose:
265
+ # Transpose = 60 - drum_root. Example: drum_root=36 (C1) → Transpose=+24,
266
+ # so triggering MIDI 36 plays the sample at original recorded pitch.
261
267
  drum_root = None
262
268
  if not is_loop:
263
269
  drum_root = _detect_drum_root_note(file_path)
264
270
  if drum_root is not None:
271
+ transpose_value = 60 - int(drum_root)
272
+ # Clamp to Simpler's Transpose range (-48..+48 semitones)
273
+ transpose_value = max(-48, min(48, transpose_value))
265
274
  hygiene_params.append(
266
- {"name_or_index": "Sample Pitch Coarse", "value": drum_root}
275
+ {"name_or_index": "Transpose", "value": transpose_value}
267
276
  )
268
277
 
269
278
  try:
@@ -158,28 +158,57 @@ def search_browser(
158
158
  return _get_ableton(ctx).send_command("search_browser", params)
159
159
 
160
160
 
161
- # Role-aware Simpler defaults BUG-2026-04-22 #17 + #18.
161
+ # M4L instrument post-load hygiene — 2026-05-02.
162
+ # Some Max-for-Live instruments load with defaults that immediately produce loud
163
+ # unwanted output (Harmonic Drone Generator from Drone Lab is the canonical
164
+ # example: Latch on + Density 80% + Volume −6 dB + all 8 voices active = a wall
165
+ # of sustained drone the moment any MIDI note touches it). Apply tames here so
166
+ # the device is workable on first load. Each entry maps a device-name match
167
+ # (substring) to a list of (parameter_name, value) pairs.
168
+ #
169
+ # Detection runs UNCONDITIONALLY (not gated on `role` like _SIMPLER_ROLE_DEFAULTS)
170
+ # because these M4L instruments are typically loaded without a role parameter.
171
+ _M4L_INSTRUMENT_HYGIENE: dict[str, list[tuple[str, float]]] = {
172
+ "Harmonic Drone Generator": [
173
+ ("Latch", 0), # Off — prevents indefinite note sustain after one trigger
174
+ ("Volume", -40), # ≈ -20 dB display (default is -18 / -6 dB which is too loud)
175
+ ("Density", 40), # 40% (default 80% is too dense for a background bed)
176
+ ],
177
+ }
178
+
179
+
180
+ # Role-aware Simpler defaults — BUG-2026-04-22 #17 + #18, plus 2026-05-02 fix.
162
181
  # Each role maps to a list of (parameter_name, value) pairs applied after
163
182
  # load via set_device_parameter. Trigger Mode polarity per BUG #9:
164
- # 0 = Trigger (one-shot), 1 = Gate (held). Volume in dB. Root in MIDI pitch.
183
+ # 0 = Trigger (one-shot), 1 = Gate (held). Volume in dB. Transpose in semitones.
184
+ #
185
+ # 2026-05-02 — fixed pitch-shift bug:
186
+ # Earlier versions used "Sample Pitch Coarse" param name, which DOES NOT EXIST
187
+ # on OriginalSimpler — the call silently raised and was swallowed. Result: every
188
+ # drum-role Simpler played 24 semitones below original pitch ("super low" sound)
189
+ # because the Simpler's default sample root is C3 (60), but drum convention sends
190
+ # MIDI 36 (C1). The correct parameter is "Transpose" (range -48..+48 semitones);
191
+ # +24 compensates for the C3-vs-C1 mismatch so drum samples play at original
192
+ # recorded pitch when MIDI 36 is sent. Melodic/texture roles use Transpose=0
193
+ # because their default playback range centers on C3 (60) — no compensation needed.
165
194
  _SIMPLER_ROLE_DEFAULTS = {
166
195
  "drum": [
167
196
  ("Snap", 0),
168
197
  ("Volume", 0.0),
169
198
  ("Trigger Mode", 0), # Trigger / one-shot
170
- ("Sample Pitch Coarse", 36), # C1, matches drum-pad convention
199
+ ("Transpose", 24), # Compensate C3-default → C1-drum-convention root
171
200
  ],
172
201
  "melodic": [
173
202
  ("Snap", 1),
174
203
  ("Volume", 0.0),
175
204
  ("Trigger Mode", 1), # Gate / held
176
- ("Sample Pitch Coarse", 60), # C3
205
+ ("Transpose", 0), # C3 default — melodic input range
177
206
  ],
178
207
  "texture": [
179
208
  ("Snap", 0),
180
209
  ("Volume", -6.0),
181
210
  ("Trigger Mode", 1), # Gate
182
- ("Sample Pitch Coarse", 60), # C3
211
+ ("Transpose", 0), # C3 default — sustained-input range
183
212
  ],
184
213
  }
185
214
 
@@ -238,26 +267,80 @@ def load_browser_item(
238
267
  "uri": uri,
239
268
  })
240
269
 
241
- # Post-load: apply role-aware defaults if the loaded device is a Simpler.
242
- if role and isinstance(result, dict) and not result.get("error"):
243
- device_index = result.get("device_index")
244
- device_class = str(result.get("class_name") or result.get("device_name") or "")
245
- if device_index is not None and "Simpler" in device_class:
246
- applied = []
247
- for name, value in _SIMPLER_ROLE_DEFAULTS[role]:
270
+ # Post-load: probe the loaded device once, then apply two layers of hygiene.
271
+ #
272
+ # 2026-05-02 — fixed device-detection bug. The TCP load_browser_item command
273
+ # returns {loaded, name, device_count} with NO device_index and NO class_name,
274
+ # so the previous detection (`result.get("device_index")` / `result.get("class_name")`)
275
+ # always failed and the role-defaults branch was never entered. Resolution:
276
+ # treat newly-loaded sample-on-empty-track as device_index=0 (Live places the
277
+ # instrument at chain head) and verify class + name via get_device_info.
278
+ #
279
+ # Layer 1 (gated on `role`): Simpler role-aware defaults — Snap/Volume/
280
+ # Trigger Mode/Transpose for drum/melodic/texture roles.
281
+ # Layer 2 (unconditional): M4L instrument hygiene — name-matched tames for
282
+ # known problem devices (Harmonic Drone Generator's Latch + loud defaults).
283
+ device_index_resolved: Optional[int] = None
284
+ device_class = ""
285
+ device_name_loaded = ""
286
+ if isinstance(result, dict) and result.get("loaded") and not result.get("error"):
287
+ device_index_resolved = result.get("device_index")
288
+ try:
289
+ probe = ableton.send_command("get_device_info", {
290
+ "track_index": track_index,
291
+ "device_index": 0,
292
+ })
293
+ device_class = str(probe.get("class_name", "") or "")
294
+ device_name_loaded = str(probe.get("name", "") or result.get("name", "") or "")
295
+ if device_index_resolved is None:
296
+ device_index_resolved = 0
297
+ except Exception:
298
+ pass
299
+
300
+ # Layer 1 — Simpler role-aware defaults
301
+ if role and device_index_resolved is not None and "Simpler" in device_class:
302
+ applied = []
303
+ for name, value in _SIMPLER_ROLE_DEFAULTS[role]:
304
+ try:
305
+ ableton.send_command("set_device_parameter", {
306
+ "track_index": track_index,
307
+ "device_index": int(device_index_resolved),
308
+ "parameter_name": name,
309
+ "value": value,
310
+ })
311
+ applied.append({"parameter": name, "value": value})
312
+ except Exception as exc:
313
+ # Don't fail the whole load if one default doesn't apply
314
+ # (parameter name might not exist on every Simpler variant).
315
+ applied.append({"parameter": name, "skipped": str(exc)})
316
+ result["role"] = role
317
+ result["role_defaults_applied"] = applied
318
+ result["device_class"] = device_class
319
+
320
+ # Layer 2 — M4L instrument hygiene (unconditional, name-matched).
321
+ # Detects Harmonic Drone Generator and other known problem M4L instruments
322
+ # by name substring, applies tame defaults to prevent loud-on-load surprises.
323
+ if device_index_resolved is not None and device_name_loaded:
324
+ for hygiene_name, params in _M4L_INSTRUMENT_HYGIENE.items():
325
+ if hygiene_name not in device_name_loaded:
326
+ continue
327
+ applied_hygiene = []
328
+ for name, value in params:
248
329
  try:
249
330
  ableton.send_command("set_device_parameter", {
250
331
  "track_index": track_index,
251
- "device_index": int(device_index),
332
+ "device_index": int(device_index_resolved),
252
333
  "parameter_name": name,
253
334
  "value": value,
254
335
  })
255
- applied.append({"parameter": name, "value": value})
336
+ applied_hygiene.append({"parameter": name, "value": value})
256
337
  except Exception as exc:
257
- # Don't fail the whole load if one default doesn't apply
258
- # (parameter name might not exist on every Simpler variant).
259
- applied.append({"parameter": name, "skipped": str(exc)})
260
- result["role"] = role
261
- result["role_defaults_applied"] = applied
338
+ applied_hygiene.append({"parameter": name, "skipped": str(exc)})
339
+ result["m4l_hygiene"] = {
340
+ "device_name": hygiene_name,
341
+ "applied": applied_hygiene,
342
+ }
343
+ result.setdefault("device_class", device_class)
344
+ break # one hygiene match per load
262
345
 
263
346
  return result
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "Agentic production system for Ableton Live 12 \u2014 459 tools, 54 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, 12 creative intelligence engines",
5
+ "description": "Agentic production system for Ableton Live 12 \u2014 462 tools, 55 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, 12 creative intelligence 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.24.0"
8
+ __version__ = "1.25.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": "459-tool agentic MCP production system for Ableton Live 12 \u2014 54 domains, 44 semantic moves, device atlas (5264 devices), Splice intelligence (gRPC + GraphQL), 9-band spectral perception auto-loaded, Creative Director skill, technique memory, 12 creative engines",
4
+ "description": "462-tool agentic MCP production system for Ableton Live 12 \u2014 55 domains, 44 semantic moves, device atlas (5264 devices), Splice intelligence (gRPC + GraphQL), 9-band spectral perception auto-loaded, Creative Director skill, technique memory, 12 creative engines",
5
5
  "repository": {
6
6
  "url": "https://github.com/dreamrec/LivePilot",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.24.0",
9
+ "version": "1.25.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "livepilot",
14
- "version": "1.24.0",
14
+ "version": "1.25.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }