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.
Files changed (113) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +10 -7
  3. package/bin/livepilot.js +97 -39
  4. package/livepilot/.Codex-plugin/plugin.json +1 -1
  5. package/livepilot/.claude-plugin/plugin.json +1 -1
  6. package/livepilot/skills/livepilot-core/SKILL.md +33 -1
  7. package/livepilot/skills/livepilot-core/references/atlas-tool-notes.md +69 -0
  8. package/livepilot/skills/livepilot-core/references/overview.md +18 -11
  9. package/livepilot/skills/livepilot-core/references/perception.md +125 -0
  10. package/livepilot/skills/livepilot-devices/references/device-parameter-units.md +66 -0
  11. package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
  12. package/livepilot/skills/livepilot-mix-engine/SKILL.md +8 -0
  13. package/livepilot/skills/livepilot-release/SKILL.md +1 -1
  14. package/livepilot/skills/livepilot-sample-engine/references/splice-tools-notes.md +56 -0
  15. package/livepilot/skills/livepilot-wonder/SKILL.md +6 -0
  16. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  17. package/m4l_device/livepilot_bridge.js +1 -1
  18. package/mcp_server/__init__.py +1 -1
  19. package/mcp_server/atlas/__init__.py +181 -40
  20. package/mcp_server/atlas/overlays.py +46 -3
  21. package/mcp_server/atlas/tools.py +226 -86
  22. package/mcp_server/audit/checks.py +50 -22
  23. package/mcp_server/audit/state.py +10 -2
  24. package/mcp_server/audit/tools.py +27 -6
  25. package/mcp_server/composer/develop/apply.py +7 -7
  26. package/mcp_server/composer/fast/apply.py +29 -23
  27. package/mcp_server/composer/framework/atlas_resolver.py +16 -1
  28. package/mcp_server/composer/full/apply.py +40 -34
  29. package/mcp_server/composer/full/engine.py +66 -32
  30. package/mcp_server/composer/tools.py +4 -3
  31. package/mcp_server/connection.py +67 -9
  32. package/mcp_server/creative_constraints/engine.py +10 -2
  33. package/mcp_server/creative_constraints/tools.py +24 -7
  34. package/mcp_server/curves.py +30 -7
  35. package/mcp_server/device_forge/builder.py +40 -11
  36. package/mcp_server/device_forge/models.py +9 -0
  37. package/mcp_server/device_forge/tools.py +30 -11
  38. package/mcp_server/experiment/engine.py +29 -22
  39. package/mcp_server/experiment/tools.py +15 -5
  40. package/mcp_server/m4l_bridge.py +69 -7
  41. package/mcp_server/memory/taste_graph.py +43 -0
  42. package/mcp_server/memory/technique_store.py +26 -3
  43. package/mcp_server/memory/tools.py +62 -4
  44. package/mcp_server/mix_engine/critics.py +187 -30
  45. package/mcp_server/mix_engine/models.py +21 -1
  46. package/mcp_server/mix_engine/state_builder.py +87 -8
  47. package/mcp_server/mix_engine/tools.py +16 -4
  48. package/mcp_server/performance_engine/tools.py +56 -8
  49. package/mcp_server/persistence/base_store.py +11 -0
  50. package/mcp_server/persistence/project_store.py +132 -8
  51. package/mcp_server/persistence/taste_store.py +90 -7
  52. package/mcp_server/preview_studio/engine.py +40 -10
  53. package/mcp_server/preview_studio/models.py +40 -0
  54. package/mcp_server/preview_studio/tools.py +56 -12
  55. package/mcp_server/project_brain/arrangement_graph.py +4 -0
  56. package/mcp_server/project_brain/models.py +2 -0
  57. package/mcp_server/project_brain/role_graph.py +13 -7
  58. package/mcp_server/reference_engine/gap_analyzer.py +58 -3
  59. package/mcp_server/reference_engine/profile_builder.py +47 -4
  60. package/mcp_server/reference_engine/tools.py +6 -0
  61. package/mcp_server/runtime/execution_router.py +51 -1
  62. package/mcp_server/runtime/tools.py +0 -1
  63. package/mcp_server/sample_engine/sources.py +0 -2
  64. package/mcp_server/sample_engine/tools.py +19 -38
  65. package/mcp_server/semantic_moves/mix_compilers.py +276 -51
  66. package/mcp_server/semantic_moves/performance_compilers.py +51 -17
  67. package/mcp_server/semantic_moves/resolvers.py +45 -0
  68. package/mcp_server/semantic_moves/sound_design_compilers.py +14 -6
  69. package/mcp_server/semantic_moves/tools.py +80 -2
  70. package/mcp_server/semantic_moves/transition_compilers.py +26 -9
  71. package/mcp_server/server.py +26 -24
  72. package/mcp_server/session_continuity/tracker.py +51 -3
  73. package/mcp_server/song_brain/builder.py +47 -5
  74. package/mcp_server/song_brain/tools.py +21 -7
  75. package/mcp_server/sound_design/critics.py +1 -0
  76. package/mcp_server/splice_client/client.py +117 -33
  77. package/mcp_server/splice_client/http_bridge.py +15 -3
  78. package/mcp_server/splice_client/quota.py +28 -0
  79. package/mcp_server/stuckness_detector/detector.py +8 -5
  80. package/mcp_server/synthesis_brain/adapters/drift.py +13 -0
  81. package/mcp_server/synthesis_brain/adapters/meld.py +13 -0
  82. package/mcp_server/tools/_analyzer_engine/sample.py +36 -10
  83. package/mcp_server/tools/_perception_engine.py +6 -0
  84. package/mcp_server/tools/agent_os.py +4 -1
  85. package/mcp_server/tools/analyzer.py +198 -209
  86. package/mcp_server/tools/arrangement.py +7 -4
  87. package/mcp_server/tools/automation.py +24 -4
  88. package/mcp_server/tools/browser.py +25 -11
  89. package/mcp_server/tools/clips.py +6 -0
  90. package/mcp_server/tools/composition.py +33 -2
  91. package/mcp_server/tools/devices.py +53 -53
  92. package/mcp_server/tools/generative.py +14 -14
  93. package/mcp_server/tools/harmony.py +7 -7
  94. package/mcp_server/tools/mixing.py +4 -4
  95. package/mcp_server/tools/planner.py +68 -6
  96. package/mcp_server/tools/research.py +20 -2
  97. package/mcp_server/tools/theory.py +10 -10
  98. package/mcp_server/tools/transport.py +7 -2
  99. package/mcp_server/transition_engine/critics.py +13 -1
  100. package/mcp_server/user_corpus/tools.py +30 -1
  101. package/mcp_server/wonder_mode/engine.py +82 -9
  102. package/mcp_server/wonder_mode/session.py +32 -10
  103. package/mcp_server/wonder_mode/tools.py +14 -1
  104. package/package.json +1 -1
  105. package/remote_script/LivePilot/__init__.py +1 -1
  106. package/remote_script/LivePilot/arrangement.py +93 -33
  107. package/remote_script/LivePilot/browser.py +60 -4
  108. package/remote_script/LivePilot/devices.py +132 -62
  109. package/remote_script/LivePilot/mixing.py +31 -5
  110. package/remote_script/LivePilot/server.py +94 -22
  111. package/remote_script/LivePilot/transport.py +11 -0
  112. package/requirements.txt +5 -5
  113. 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 (e.g., freeze renders audio)
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
- self._log("Client buffer overflow disconnecting")
297
- break
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
- cancelled = threading.Event()
356
- self._command_queue.put((command, response_queue, cancelled))
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
- # Mark the queued command as abandoned. If it hasn't been dequeued
394
- # yet (or is awaiting its settle hop), _process_next_command sees
395
- # the flag and skips router.dispatch preventing a write that the
396
- # client was just told timed out from executing on Live's main
397
- # thread later. If dispatch already happened, setting the flag is
398
- # a harmless no-op.
399
- cancelled.set()
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, cancelled = self._command_queue.get_nowait()
472
+ command, response_queue, state = self._command_queue.get_nowait()
415
473
  except queue.Empty:
416
474
  return
417
475
 
418
- # The TCP thread already gave up on this command (timed out) and told
419
- # the client so. Do NOT dispatch it running the write now would be a
420
- # phantom mutation. Nobody is waiting on response_queue, so just keep
421
- # the main-thread pump alive by draining whatever is left.
422
- if cancelled.is_set():
423
- self._drain_queue()
424
- return
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 Exception as exc:
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.4.6
3
- fastmcp>=3.4.2,<3.5.0 # pinned upper bound — _get_all_tools() accesses private internals
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.17.1
10
- mutagen>=1.47.0
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.81.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.2",
9
+ "version": "1.27.3",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "livepilot",
14
- "version": "1.27.1",
14
+ "version": "1.27.3",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }