livepilot 1.27.2 → 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.
- package/CHANGELOG.md +66 -0
- package/README.md +10 -7
- package/bin/livepilot.js +97 -39
- package/livepilot/.Codex-plugin/plugin.json +1 -1
- package/livepilot/.claude-plugin/plugin.json +1 -1
- package/livepilot/skills/livepilot-core/SKILL.md +33 -1
- package/livepilot/skills/livepilot-core/references/atlas-tool-notes.md +69 -0
- package/livepilot/skills/livepilot-core/references/overview.md +18 -11
- package/livepilot/skills/livepilot-core/references/perception.md +125 -0
- package/livepilot/skills/livepilot-devices/references/device-parameter-units.md +66 -0
- package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
- package/livepilot/skills/livepilot-mix-engine/SKILL.md +8 -0
- package/livepilot/skills/livepilot-release/SKILL.md +1 -1
- package/livepilot/skills/livepilot-sample-engine/references/splice-tools-notes.md +56 -0
- package/livepilot/skills/livepilot-wonder/SKILL.md +6 -0
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/atlas/__init__.py +181 -40
- package/mcp_server/atlas/overlays.py +46 -3
- package/mcp_server/atlas/tools.py +226 -86
- package/mcp_server/audit/checks.py +50 -22
- package/mcp_server/audit/state.py +10 -2
- package/mcp_server/audit/tools.py +27 -6
- package/mcp_server/composer/develop/apply.py +7 -7
- package/mcp_server/composer/fast/apply.py +29 -23
- package/mcp_server/composer/framework/atlas_resolver.py +16 -1
- package/mcp_server/composer/full/apply.py +40 -34
- package/mcp_server/composer/full/engine.py +66 -32
- package/mcp_server/composer/tools.py +4 -3
- package/mcp_server/connection.py +67 -9
- package/mcp_server/creative_constraints/engine.py +10 -2
- package/mcp_server/creative_constraints/tools.py +24 -7
- package/mcp_server/curves.py +30 -7
- package/mcp_server/device_forge/builder.py +40 -11
- package/mcp_server/device_forge/models.py +9 -0
- package/mcp_server/device_forge/tools.py +30 -11
- package/mcp_server/experiment/engine.py +29 -22
- package/mcp_server/experiment/tools.py +15 -5
- package/mcp_server/m4l_bridge.py +69 -7
- package/mcp_server/memory/taste_graph.py +43 -0
- package/mcp_server/memory/technique_store.py +26 -3
- package/mcp_server/memory/tools.py +62 -4
- package/mcp_server/mix_engine/critics.py +187 -30
- package/mcp_server/mix_engine/models.py +21 -1
- package/mcp_server/mix_engine/state_builder.py +87 -8
- package/mcp_server/mix_engine/tools.py +16 -4
- package/mcp_server/performance_engine/tools.py +56 -8
- package/mcp_server/persistence/base_store.py +11 -0
- package/mcp_server/persistence/project_store.py +132 -8
- package/mcp_server/persistence/taste_store.py +90 -7
- package/mcp_server/preview_studio/engine.py +40 -10
- package/mcp_server/preview_studio/models.py +40 -0
- package/mcp_server/preview_studio/tools.py +56 -12
- package/mcp_server/project_brain/arrangement_graph.py +4 -0
- package/mcp_server/project_brain/models.py +2 -0
- package/mcp_server/project_brain/role_graph.py +13 -7
- package/mcp_server/reference_engine/gap_analyzer.py +58 -3
- package/mcp_server/reference_engine/profile_builder.py +47 -4
- package/mcp_server/reference_engine/tools.py +6 -0
- package/mcp_server/runtime/execution_router.py +51 -1
- package/mcp_server/runtime/tools.py +0 -1
- package/mcp_server/sample_engine/sources.py +0 -2
- package/mcp_server/sample_engine/tools.py +19 -38
- package/mcp_server/semantic_moves/mix_compilers.py +276 -51
- package/mcp_server/semantic_moves/performance_compilers.py +51 -17
- package/mcp_server/semantic_moves/resolvers.py +45 -0
- package/mcp_server/semantic_moves/sound_design_compilers.py +14 -6
- package/mcp_server/semantic_moves/tools.py +80 -2
- package/mcp_server/semantic_moves/transition_compilers.py +26 -9
- package/mcp_server/server.py +26 -24
- package/mcp_server/session_continuity/tracker.py +51 -3
- package/mcp_server/song_brain/builder.py +47 -5
- package/mcp_server/song_brain/tools.py +21 -7
- package/mcp_server/sound_design/critics.py +1 -0
- package/mcp_server/splice_client/client.py +117 -33
- package/mcp_server/splice_client/http_bridge.py +15 -3
- package/mcp_server/splice_client/quota.py +28 -0
- package/mcp_server/stuckness_detector/detector.py +8 -5
- package/mcp_server/synthesis_brain/adapters/drift.py +13 -0
- package/mcp_server/synthesis_brain/adapters/meld.py +13 -0
- package/mcp_server/tools/_analyzer_engine/sample.py +36 -10
- package/mcp_server/tools/_perception_engine.py +6 -0
- package/mcp_server/tools/agent_os.py +4 -1
- package/mcp_server/tools/analyzer.py +198 -209
- package/mcp_server/tools/arrangement.py +7 -4
- package/mcp_server/tools/automation.py +24 -4
- package/mcp_server/tools/browser.py +25 -11
- package/mcp_server/tools/clips.py +6 -0
- package/mcp_server/tools/composition.py +33 -2
- package/mcp_server/tools/devices.py +53 -53
- package/mcp_server/tools/generative.py +14 -14
- package/mcp_server/tools/harmony.py +7 -7
- package/mcp_server/tools/mixing.py +4 -4
- package/mcp_server/tools/planner.py +68 -6
- package/mcp_server/tools/research.py +20 -2
- package/mcp_server/tools/theory.py +10 -10
- package/mcp_server/tools/transport.py +7 -2
- package/mcp_server/transition_engine/critics.py +13 -1
- package/mcp_server/user_corpus/tools.py +30 -1
- package/mcp_server/wonder_mode/engine.py +82 -9
- package/mcp_server/wonder_mode/session.py +32 -10
- package/mcp_server/wonder_mode/tools.py +14 -1
- package/package.json +1 -1
- package/remote_script/LivePilot/__init__.py +1 -1
- package/remote_script/LivePilot/arrangement.py +93 -33
- package/remote_script/LivePilot/browser.py +60 -4
- package/remote_script/LivePilot/devices.py +132 -62
- package/remote_script/LivePilot/mixing.py +31 -5
- package/remote_script/LivePilot/server.py +94 -22
- package/remote_script/LivePilot/transport.py +11 -0
- package/requirements.txt +5 -5
- package/server.json +2 -2
|
@@ -60,6 +60,18 @@ WRITE_COMMANDS = frozenset([
|
|
|
60
60
|
"clear_clip_automation",
|
|
61
61
|
])
|
|
62
62
|
|
|
63
|
+
# P2-48 backstop: "undo"/"redo" are bare verbs — no read/write prefix applies
|
|
64
|
+
# to them, so they only ever classified as writes via the literals inside
|
|
65
|
+
# WRITE_COMMANDS above. If a future WRITE_COMMANDS trim ever dropped them
|
|
66
|
+
# (e.g. a well-intentioned dedupe pass), is_write_command() would silently
|
|
67
|
+
# reclassify genuinely-mutating commands as reads, reintroducing the
|
|
68
|
+
# read-after-write race the settle delay exists to prevent. This frozenset is
|
|
69
|
+
# checked independently of WRITE_COMMANDS so that can't happen silently.
|
|
70
|
+
WRITE_BARE_VERBS = frozenset([
|
|
71
|
+
"undo",
|
|
72
|
+
"redo",
|
|
73
|
+
])
|
|
74
|
+
|
|
63
75
|
# Future-safe write detection. WRITE_COMMANDS remains the explicit allow-list
|
|
64
76
|
# for older handlers and readability; the prefix classifier catches newer
|
|
65
77
|
# mutating handlers so they still receive the write timeout and settle delay.
|
|
@@ -74,6 +86,7 @@ WRITE_COMMAND_PREFIXES = (
|
|
|
74
86
|
"arrangement_automation_",
|
|
75
87
|
"assign_",
|
|
76
88
|
"back_to_",
|
|
89
|
+
"batch_",
|
|
77
90
|
"capture_",
|
|
78
91
|
"cleanup_",
|
|
79
92
|
"clear_",
|
|
@@ -112,6 +125,9 @@ WRITE_COMMAND_PREFIXES = (
|
|
|
112
125
|
|
|
113
126
|
def is_write_command(command_type):
|
|
114
127
|
"""Return True if a command is expected to mutate Live state."""
|
|
128
|
+
# Checked first and independent of WRITE_COMMANDS — see WRITE_BARE_VERBS.
|
|
129
|
+
if command_type in WRITE_BARE_VERBS:
|
|
130
|
+
return True
|
|
115
131
|
if command_type in WRITE_COMMANDS:
|
|
116
132
|
return True
|
|
117
133
|
if command_type in READ_ONLY_COMMANDS:
|
|
@@ -121,12 +137,34 @@ def is_write_command(command_type):
|
|
|
121
137
|
return command_type.startswith(WRITE_COMMAND_PREFIXES)
|
|
122
138
|
|
|
123
139
|
|
|
124
|
-
# Commands that need longer timeouts (
|
|
140
|
+
# Commands that need longer timeouts (freeze renders audio; flatten runs
|
|
141
|
+
# track.flatten() synchronously in-call)
|
|
125
142
|
SLOW_WRITE_COMMANDS = frozenset([
|
|
126
143
|
"freeze_track",
|
|
144
|
+
"flatten_track",
|
|
127
145
|
])
|
|
128
146
|
|
|
129
147
|
|
|
148
|
+
class _CmdState(object):
|
|
149
|
+
"""Shared cancel/commit state for one queued command.
|
|
150
|
+
|
|
151
|
+
The TCP thread (which waits for the response and may time out) and the
|
|
152
|
+
main thread (which dispatches the command) both touch this object. The
|
|
153
|
+
lock makes "cancel" and "commit" mutually exclusive so they cannot race:
|
|
154
|
+
a write is either cancelled BEFORE the main thread commits to dispatching
|
|
155
|
+
it (so it never runs — no phantom mutation after a reported TIMEOUT), or
|
|
156
|
+
the main thread commits first (so a late timeout cannot cancel a write
|
|
157
|
+
that is already going to run). The two flags can never both be True.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
__slots__ = ("lock", "cancelled", "committed")
|
|
161
|
+
|
|
162
|
+
def __init__(self):
|
|
163
|
+
self.lock = threading.Lock()
|
|
164
|
+
self.cancelled = False
|
|
165
|
+
self.committed = False
|
|
166
|
+
|
|
167
|
+
|
|
130
168
|
class LivePilotServer(object):
|
|
131
169
|
"""TCP server that bridges JSON commands to Ableton's main thread.
|
|
132
170
|
|
|
@@ -292,9 +330,25 @@ class LivePilotServer(object):
|
|
|
292
330
|
if not data:
|
|
293
331
|
break
|
|
294
332
|
buf.extend(data)
|
|
295
|
-
if len(buf) > MAX_BUF:
|
|
296
|
-
|
|
297
|
-
|
|
333
|
+
if len(buf) > MAX_BUF and buf.find(b"\n") < 0:
|
|
334
|
+
# A single request line exceeded MAX_BUF with no framing
|
|
335
|
+
# newline. Emit a structured error (mirroring the
|
|
336
|
+
# UnicodeDecodeError branch below) and resynchronize the
|
|
337
|
+
# stream by dropping the oversized buffer, rather than a
|
|
338
|
+
# bare disconnect the client can't distinguish from a
|
|
339
|
+
# crash or stolen socket.
|
|
340
|
+
self._log("Request line exceeded %d bytes — dropping" % MAX_BUF)
|
|
341
|
+
del buf[:]
|
|
342
|
+
self._send(client, {
|
|
343
|
+
"id": "unknown",
|
|
344
|
+
"ok": False,
|
|
345
|
+
"error": {
|
|
346
|
+
"code": "INVALID_PARAM",
|
|
347
|
+
"message": "Request line exceeded %d bytes "
|
|
348
|
+
"without a newline" % MAX_BUF,
|
|
349
|
+
},
|
|
350
|
+
})
|
|
351
|
+
continue
|
|
298
352
|
while True:
|
|
299
353
|
nl = buf.find(b"\n")
|
|
300
354
|
if nl < 0:
|
|
@@ -352,8 +406,8 @@ class LivePilotServer(object):
|
|
|
352
406
|
# (now-abandoned) dispatch instead of mutating Live after the client
|
|
353
407
|
# has already been told the command timed out (phantom write).
|
|
354
408
|
response_queue = queue.Queue()
|
|
355
|
-
|
|
356
|
-
self._command_queue.put((command, response_queue,
|
|
409
|
+
state = _CmdState()
|
|
410
|
+
self._command_queue.put((command, response_queue, state))
|
|
357
411
|
|
|
358
412
|
# Schedule processing on Ableton's main thread
|
|
359
413
|
try:
|
|
@@ -390,13 +444,17 @@ class LivePilotServer(object):
|
|
|
390
444
|
try:
|
|
391
445
|
resp = response_queue.get(timeout=timeout)
|
|
392
446
|
except queue.Empty:
|
|
393
|
-
#
|
|
394
|
-
#
|
|
395
|
-
#
|
|
396
|
-
#
|
|
397
|
-
#
|
|
398
|
-
#
|
|
399
|
-
|
|
447
|
+
# Abandon the queued command — but ATOMICALLY. We only mark it
|
|
448
|
+
# cancelled if the main thread has not already committed to
|
|
449
|
+
# dispatching it. Holding state.lock across the check+set makes
|
|
450
|
+
# this mutually exclusive with _process_next_command's commit, so
|
|
451
|
+
# there is no window where a write dispatches even though the TCP
|
|
452
|
+
# thread intended to cancel it. If the main thread already
|
|
453
|
+
# committed (committed is True), the write is legitimately in
|
|
454
|
+
# flight and we simply report the timeout without cancelling.
|
|
455
|
+
with state.lock:
|
|
456
|
+
if not state.committed:
|
|
457
|
+
state.cancelled = True
|
|
400
458
|
resp = {
|
|
401
459
|
"id": request_id,
|
|
402
460
|
"ok": False,
|
|
@@ -411,17 +469,22 @@ class LivePilotServer(object):
|
|
|
411
469
|
"""Called on Ableton's main thread via schedule_message.
|
|
412
470
|
Processes one command from the queue."""
|
|
413
471
|
try:
|
|
414
|
-
command, response_queue,
|
|
472
|
+
command, response_queue, state = self._command_queue.get_nowait()
|
|
415
473
|
except queue.Empty:
|
|
416
474
|
return
|
|
417
475
|
|
|
418
|
-
#
|
|
419
|
-
#
|
|
420
|
-
#
|
|
421
|
-
#
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
476
|
+
# Atomically decide whether to run this command. If the TCP thread
|
|
477
|
+
# already gave up (timed out) and cancelled it, skip the dispatch —
|
|
478
|
+
# running the write now would be a phantom mutation after the client
|
|
479
|
+
# was told TIMEOUT. Otherwise mark it committed UNDER THE SAME LOCK so
|
|
480
|
+
# a concurrent timeout cannot cancel a write that is about to run.
|
|
481
|
+
# Once committed is True, state.cancelled can never become True, so
|
|
482
|
+
# the settle-hop send_response below always targets a live decision.
|
|
483
|
+
with state.lock:
|
|
484
|
+
if state.cancelled:
|
|
485
|
+
self._drain_queue()
|
|
486
|
+
return
|
|
487
|
+
state.committed = True
|
|
425
488
|
|
|
426
489
|
cmd_type = command.get("type", "")
|
|
427
490
|
is_write = is_write_command(cmd_type)
|
|
@@ -429,7 +492,16 @@ class LivePilotServer(object):
|
|
|
429
492
|
try:
|
|
430
493
|
song = self._cs.song()
|
|
431
494
|
result = router.dispatch(song, command)
|
|
432
|
-
except
|
|
495
|
+
except BaseException as exc:
|
|
496
|
+
# P3-41: catch BaseException, not just Exception. If a handler
|
|
497
|
+
# raised a non-Exception BaseException (SystemExit / KeyboardInterrupt
|
|
498
|
+
# / GeneratorExit), `result` would never be assigned, the response/
|
|
499
|
+
# drain block below would never run, and the TCP thread would hang
|
|
500
|
+
# on response_queue.get() to its full timeout WHILE the command queue
|
|
501
|
+
# stalls. Converting any dispatch failure into a structured INTERNAL
|
|
502
|
+
# result guarantees the caller is unblocked and the queue advances.
|
|
503
|
+
# (Ableton's embedded main-thread processor has no legitimate
|
|
504
|
+
# SystemExit/KeyboardInterrupt to propagate.)
|
|
433
505
|
result = {
|
|
434
506
|
"id": command.get("id", "unknown"),
|
|
435
507
|
"ok": False,
|
|
@@ -30,6 +30,17 @@ def get_session_info(song, params):
|
|
|
30
30
|
track_data["arm"] = None
|
|
31
31
|
track_data["has_midi_input"] = None
|
|
32
32
|
track_data["has_audio_input"] = None
|
|
33
|
+
# P2-21: semantic-move compilers (mix/sound-design/transition) need
|
|
34
|
+
# each track's CURRENT volume to compile RELATIVE nudges instead of
|
|
35
|
+
# absolute overwrites — e.g. "make it punchier" must not slam a
|
|
36
|
+
# hot drum bus down to a flat level. Guarded the same way as the
|
|
37
|
+
# arm/has_midi_input/has_audio_input block above: some track types
|
|
38
|
+
# (or older test doubles) may not expose mixer_device at all, and
|
|
39
|
+
# that must degrade to None rather than aborting the whole scan.
|
|
40
|
+
try:
|
|
41
|
+
track_data["volume"] = track.mixer_device.volume.value
|
|
42
|
+
except Exception:
|
|
43
|
+
track_data["volume"] = None
|
|
33
44
|
tracks_info.append(track_data)
|
|
34
45
|
|
|
35
46
|
return_tracks_info = []
|
package/requirements.txt
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# LivePilot MCP Server dependencies
|
|
2
|
-
numpy>=2.
|
|
3
|
-
fastmcp>=3.4.
|
|
2
|
+
numpy>=2.5.1
|
|
3
|
+
fastmcp>=3.4.4,<3.5.0 # pinned upper bound — _get_all_tools() accesses private internals
|
|
4
4
|
midiutil>=1.2.1
|
|
5
5
|
pretty_midi>=0.2.11
|
|
6
6
|
# v1.8 Perception Layer (offline analysis)
|
|
7
7
|
pyloudnorm>=0.2.0
|
|
8
8
|
soundfile>=0.14.0
|
|
9
|
-
scipy>=1.
|
|
10
|
-
mutagen>=1.
|
|
9
|
+
scipy>=1.18.0
|
|
10
|
+
mutagen>=1.48.1
|
|
11
11
|
# v1.10.5 Splice online catalog integration — required, not optional.
|
|
12
12
|
# Without these, SpliceGRPCClient silently disables itself and search_samples
|
|
13
13
|
# falls back to the SQLite sounds.db which only returns locally downloaded
|
|
14
14
|
# samples (see docs/2026-04-14-bugs-discovered.md — P0-2).
|
|
15
|
-
grpcio>=1.
|
|
15
|
+
grpcio>=1.82.1
|
|
16
16
|
protobuf>=7.35.1
|
|
17
17
|
|
|
18
18
|
# Known benign warning during install:
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/dreamrec/LivePilot",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.27.
|
|
9
|
+
"version": "1.27.3",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "livepilot",
|
|
14
|
-
"version": "1.27.
|
|
14
|
+
"version": "1.27.3",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|