livepilot 1.9.17 → 1.9.18

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,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
3
3
  "name": "dreamrec-LivePilot",
4
- "description": "Agentic MCP production system for Ableton Live 12 — 236 tools, 32 domains",
4
+ "description": "Agentic MCP production system for Ableton Live 12 — 236 tools, 31 domains",
5
5
  "owner": {
6
6
  "name": "dreamrec",
7
7
  "email": "dreamrec@users.noreply.github.com"
@@ -9,8 +9,8 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "livepilot",
12
- "description": "Agentic production system for Ableton Live 12 — 236 tools, 32 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
13
- "version": "1.9.17",
12
+ "description": "Agentic production system for Ableton Live 12 — 236 tools, 31 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
13
+ "version": "1.9.18",
14
14
  "author": {
15
15
  "name": "Pilot Studio"
16
16
  },
package/AGENTS.md CHANGED
@@ -1,4 +1,4 @@
1
- # LivePilot v1.9.17 — Ableton Live 12
1
+ # LivePilot v1.9.18 — Ableton Live 12
2
2
 
3
3
  ## Project
4
4
  - **Repo:** This directory (LivePilot)
@@ -22,7 +22,7 @@
22
22
  ## Key Rules
23
23
  - ALL Live Object Model (LOM) calls must execute on Ableton's main thread via schedule_message queue
24
24
  - Live 12 minimum — use modern note API (add_new_notes, get_notes_extended, apply_note_modifications)
25
- - 236 tools across 32 domains: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, midi_io, perception, agent_os, composition, motif, research, planner, project_brain, runtime, evaluation, memory_fabric, mix_engine, sound_design, transition_engine, reference_engine, translation_engine, performance_engine
25
+ - 236 tools across 31 domains: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, midi_io, perception, agent_os, composition, motif, research, planner, project_brain, runtime, evaluation, mix_engine, sound_design, transition_engine, reference_engine, translation_engine, performance_engine
26
26
  - JSON over TCP, newline-delimited, port 9878
27
27
  - Structured errors with codes: INDEX_ERROR, NOT_FOUND, INVALID_PARAM, STATE_ERROR, TIMEOUT, INTERNAL
28
28
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.18 — Deep Audit Fix Pass (April 2026)
4
+
5
+ ### Critical Fixes
6
+ - **fix(tracks):** monitoring enum mismatch — MCP advertised `0=Off,1=In,2=Auto` but Remote Script uses `0=In,1=Auto,2=Off`; clients deterministically chose wrong mode
7
+ - **fix(connection):** retry logic could replay mutating commands after `sendall` succeeded — added `_send_completed` flag to prevent double mutations
8
+ - **fix(m4l_bridge):** `capture_stop` cancelled in-flight capture future instead of resolving it — callers got `CancelledError` instead of partial result
9
+
10
+ ### Medium Fixes
11
+ - **fix(skills):** removed 6 phantom tool names from speed tiers (`analyze_dynamic_range`, `analyze_spectral_evolution`, `separate_stems`, `diagnose_mix`, `transcribe_to_midi`, `compare_loudness`)
12
+ - **fix(clip_automation):** added `int()` casts to `send_index`, `device_index`, `parameter_index` — prevented TypeError when MCP sends strings
13
+ - **fix(arrangement):** `add_arrangement_notes` now supports `probability`, `velocity_deviation`, `release_velocity` (parity with session `add_notes`)
14
+ - **fix(devices/browser):** reset `_iterations` counter per category in URI search — prevented premature cutoff for devices in later categories
15
+ - **fix(imports):** standardized 6 engine files from `mcp.server.fastmcp` to `fastmcp` import path
16
+ - **fix(docs):** corrected domain count from 32 to 31 (`memory_fabric` is an alias for `memory`) across 17 files
17
+ - **fix(server.json):** added missing `, MIDI I/O` to description to match package.json
18
+
19
+ ### Low Fixes
20
+ - **fix(clips):** `delete_clip` now checks `has_clip` before deleting
21
+ - **fix(arrangement):** `back_to_arranger` no longer reads write-only trigger property
22
+ - **fix(utils):** return track error message no longer shows `(0..-1)` when none exist
23
+ - **fix(connection):** removed dead `send_command_async` and unused `asyncio` import
24
+
3
25
  ## 1.9.17 — Skills Architecture V2 (April 2026)
4
26
 
5
27
  ### Skills (9 new, 1 slimmed)
@@ -106,7 +128,7 @@
106
128
  - Fix(Med): Ledger key mismatch — memory promotion now reads correct 'action_ledger' key
107
129
 
108
130
  ### Stats
109
- - 236 tools across 32 domains (was 194)
131
+ - 236 tools across 31 domains (was 194)
110
132
  - 1,014 tests passing (was ~400)
111
133
  - 12 new engine packages
112
134
  - 36 new MCP tools
package/README.md CHANGED
@@ -50,7 +50,7 @@
50
50
  │ ▼ │
51
51
  │ ┌─────────────────┐ │
52
52
  │ │ 236 MCP Tools │ │
53
- │ │ 32 domains │ │
53
+ │ │ 31 domains │ │
54
54
  │ └────────┬────────┘ │
55
55
  │ │ │
56
56
  │ Remote Script ──┤── TCP 9878 │
@@ -79,7 +79,7 @@ All three feed into 236 deterministic tools that execute on Ableton's main threa
79
79
 
80
80
  ## Tools
81
81
 
82
- 236 tools across 32 domains. Highlights below — [full catalog here](docs/manual/tool-catalog.md).
82
+ 236 tools across 31 domains. Highlights below — [full catalog here](docs/manual/tool-catalog.md).
83
83
 
84
84
  <br>
85
85
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.9.17",
4
- "description": "Agentic production system for Ableton Live 12 — 236 tools, 32 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
3
+ "version": "1.9.18",
4
+ "description": "Agentic production system for Ableton Live 12 — 236 tools, 31 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
5
  "author": {
6
6
  "name": "Pilot Studio"
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.9.17",
4
- "description": "Agentic production system for Ableton Live 12 — 236 tools, 32 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
3
+ "version": "1.9.18",
4
+ "description": "Agentic production system for Ableton Live 12 — 236 tools, 31 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
5
  "author": {
6
6
  "name": "Pilot Studio"
7
7
  }
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: livepilot-core
3
- description: Core discipline for LivePilot — agentic production system for Ableton Live 12. 236 tools across 32 domains. This skill should be used whenever working with Ableton Live through MCP tools. Provides golden rules, tool speed tiers, error handling protocol, and pointers to domain and engine skills.
3
+ description: Core discipline for LivePilot — agentic production system for Ableton Live 12. 236 tools across 31 domains. This skill should be used whenever working with Ableton Live through MCP tools. Provides golden rules, tool speed tiers, error handling protocol, and pointers to domain and engine skills.
4
4
  ---
5
5
 
6
6
  # LivePilot Core — Ableton Live 12
7
7
 
8
- Agentic production system for Ableton Live 12. 236 tools across 32 domains, three layers:
8
+ Agentic production system for Ableton Live 12. 236 tools across 31 domains, three layers:
9
9
 
10
10
  - **Device Atlas** — 280+ instruments, 139 drum kits, 350+ impulse responses. Consult `references/device-atlas/` before loading any device. Never guess a device name.
11
11
  - **M4L Analyzer** — Real-time audio analysis on the master bus (8-band spectrum, RMS/peak, key detection). Optional — all core tools work without it.
@@ -34,20 +34,16 @@ Agentic production system for Ableton Live 12. 236 tools across 32 domains, thre
34
34
  All 236 core tools plus M4L perception tools.
35
35
 
36
36
  ### Fast (1-5s) — Use freely
37
- `analyze_loudness` · `analyze_dynamic_range` · `compare_loudness`
37
+ `analyze_loudness` · `analyze_mix` · `analyze_sound_design`
38
38
 
39
39
  ### Slow (5-15s) — Tell the user first
40
- `analyze_spectral_evolution` · `compare_to_reference` · `transcribe_to_midi`
41
-
42
- ### Heavy (30-120s) — ALWAYS ask first
43
- `separate_stems` · `diagnose_mix` — GPU-intensive. Never call speculatively.
40
+ `compare_to_reference` · `analyze_spectrum_offline` · `read_audio_metadata`
44
41
 
45
42
  **Escalation pattern:** Start fast, escalate only with consent:
46
43
  ```
47
44
  Level 1 (instant): get_master_spectrum + get_track_meters
48
- Level 2 (fast): analyze_loudness + analyze_dynamic_range
49
- Level 3 (slow): analyze_spectral_evolution + compare_to_reference
50
- Level 4 (heavy): separate_stems → diagnose_mix
45
+ Level 2 (fast): analyze_loudness + analyze_mix
46
+ Level 3 (slow): compare_to_reference + analyze_spectrum_offline
51
47
  ```
52
48
 
53
49
  ## Error Handling Protocol
@@ -1,6 +1,6 @@
1
- # LivePilot v1.9.17 — Architecture & Tool Reference
1
+ # LivePilot v1.9.18 — Architecture & Tool Reference
2
2
 
3
- Agentic production system for Ableton Live 12. 236 tools across 32 domains. Device atlas (280+ devices), spectral perception (M4L analyzer), technique memory, automation intelligence (16 curve types, 15 recipes), music theory (Krumhansl-Schmuckler, species counterpoint), generative algorithms (Euclidean rhythm, tintinnabuli, phase shift, additive process), neo-Riemannian harmony (PRL transforms, Tonnetz), MIDI file I/O.
3
+ Agentic production system for Ableton Live 12. 236 tools across 31 domains. Device atlas (280+ devices), spectral perception (M4L analyzer), technique memory, automation intelligence (16 curve types, 15 recipes), music theory (Krumhansl-Schmuckler, species counterpoint), generative algorithms (Euclidean rhythm, tintinnabuli, phase shift, additive process), neo-Riemannian harmony (PRL transforms, Tonnetz), MIDI file I/O.
4
4
 
5
5
  ## Architecture
6
6
 
@@ -104,7 +104,7 @@ Call `get_capability_state` at the start of any evaluation session. The response
104
104
  {
105
105
  "mode": "normal",
106
106
  "analyzer_connected": true,
107
- "bridge_version": "1.9.17",
107
+ "bridge_version": "1.9.18",
108
108
  "spectral_cache_age_ms": 1200,
109
109
  "flucoma_available": false,
110
110
  "session_connected": true
@@ -145,9 +145,8 @@ Use the mix engine when the user wants a critical evaluation of their mix, not j
145
145
  Follow this progression — start fast, go deeper only when needed:
146
146
 
147
147
  1. **Instant:** `get_master_spectrum` + `get_track_meters` — frequency balance + levels. Answers 80% of mix questions.
148
- 2. **Fast (1-5s):** `analyze_loudness` + `analyze_dynamic_range` — LUFS, true peak, LRA, crest factor. For mastering prep.
149
- 3. **Slow (5-15s):** `analyze_spectral_evolution` + `compare_to_reference` — timbral trends, reference matching. Ask the user first.
150
- 4. **Heavy (30-120s):** `separate_stems` + `diagnose_mix` — full diagnostic. Requires explicit user consent.
148
+ 2. **Fast (1-5s):** `analyze_loudness` + `analyze_mix` — LUFS, true peak, and full mix analysis. For mastering prep.
149
+ 3. **Slow (5-15s):** `compare_to_reference` + `analyze_spectrum_offline` — reference matching, offline spectral analysis. Ask the user first.
151
150
 
152
151
  Never skip levels. Start at the lowest appropriate level and offer to go deeper.
153
152
 
@@ -28,21 +28,21 @@ Run this checklist EVERY time the user says "update everything", "push", "releas
28
28
 
29
29
  ## 2. Tool Count (must ALL match)
30
30
 
31
- Current: **236 tools across 32 domains**.
31
+ Current: **236 tools across 31 domains**.
32
32
  Core (no M4L): **149**. Analyzer (M4L): **29**. Perception (offline): **4**.
33
33
 
34
34
  Verify: `grep -rc "@mcp.tool" mcp_server/tools/ | grep -v ":0" | awk -F: '{sum+=$2} END{print sum}'`
35
35
 
36
36
  Files that reference tool count:
37
37
  - [ ] `README.md` — header, PERCEPTION section ("207 core...29 analyzer"), Analyzer table header "(29)", Perception table header "(4)"
38
- - [ ] `package.json` → `"description"` (236 tools, 32 domains)
38
+ - [ ] `package.json` → `"description"` (236 tools, 31 domains)
39
39
  - [ ] `server.json` → `"description"`
40
40
  - [ ] `livepilot/.Codex-plugin/plugin.json` → `"description"` (primary Codex manifest)
41
41
  - [ ] `livepilot/.claude-plugin/plugin.json` → `"description"` (must match Codex plugin)
42
42
  - [ ] `.claude-plugin/marketplace.json` → `"description"`
43
- - [ ] `CLAUDE.md` → "236 tools across 32 domains"
44
- - [ ] `livepilot/skills/livepilot-core/SKILL.md` — "236 tools across 32 domains", Analyzer (29), Perception (4)
45
- - [ ] `livepilot/skills/livepilot-core/references/overview.md` — "236 tools across 32 domains"
43
+ - [ ] `CLAUDE.md` → "236 tools across 31 domains"
44
+ - [ ] `livepilot/skills/livepilot-core/SKILL.md` — "236 tools across 31 domains", Analyzer (29), Perception (4)
45
+ - [ ] `livepilot/skills/livepilot-core/references/overview.md` — "236 tools across 31 domains"
46
46
  - [ ] `docs/manual/index.md` — domain table: Analyzer (29), Perception (4)
47
47
  - [ ] `docs/manual/getting-started.md` — "207 core tools...29 analyzer"
48
48
  - [ ] `docs/manual/tool-reference.md` — all domains present with correct counts
@@ -56,10 +56,10 @@ Files that reference tool count:
56
56
 
57
57
  ## 3. Domain Count
58
58
 
59
- Current: **32 domains**: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, midi_io, perception, agent_os, composition, research, planner, project_brain, runtime, evaluation, memory_fabric, mix_engine, sound_design, transition_engine, reference_engine, translation_engine, performance_engine.
59
+ Current: **31 domains**: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, midi_io, perception, agent_os, composition, motif, research, planner, project_brain, runtime, evaluation, mix_engine, sound_design, transition_engine, reference_engine, translation_engine, performance_engine.
60
60
 
61
- - [ ] All files that mention domain count say "32 domains"
62
- - [ ] Domain lists include ALL 32 (especially newer domains — they're the most often omitted)
61
+ - [ ] All files that mention domain count say "31 domains"
62
+ - [ ] Domain lists include ALL 31 (especially newer domains — they're the most often omitted)
63
63
 
64
64
  ## 4. npm Registry
65
65
 
@@ -89,8 +89,8 @@ Current: **32 domains**: transport, tracks, clips, notes, devices, scenes, mixin
89
89
 
90
90
  - [ ] `README.md` — features match current capabilities, "Coming" section is accurate
91
91
  - [ ] `docs/manual/getting-started.md` — install instructions current
92
- - [ ] `docs/manual/tool-reference.md` — all 32 domains listed, all 236 tools present
93
- - [ ] `docs/TOOL_REFERENCE.md` — all 32 domains present
92
+ - [ ] `docs/manual/tool-reference.md` — all 31 domains listed, all 236 tools present
93
+ - [ ] `docs/TOOL_REFERENCE.md` — all 31 domains present
94
94
  - [ ] `docs/M4L_BRIDGE.md` — architecture accurate, core tool count correct
95
95
 
96
96
  ## 9. Derived Artifacts
@@ -84,7 +84,7 @@ function anything() {
84
84
  function dispatch(cmd, args) {
85
85
  switch(cmd) {
86
86
  case "ping":
87
- send_response({"ok": true, "version": "1.9.17"});
87
+ send_response({"ok": true, "version": "1.9.18"});
88
88
  break;
89
89
  case "get_params":
90
90
  cmd_get_params(args);
@@ -1,2 +1,2 @@
1
1
  """LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
2
- __version__ = "1.9.17"
2
+ __version__ = "1.9.18"
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import asyncio
6
5
  import json
7
6
  import os
8
7
  import socket
@@ -162,15 +161,20 @@ class AbletonConnection:
162
161
  try:
163
162
  response = self._send_raw(command)
164
163
  except AbletonConnectionError as exc:
165
- # Don't retry timeouts Ableton may have processed the command
164
+ # If the send phase succeeded (data left this process),
165
+ # Ableton may have already applied the command. Never
166
+ # replay — the duplicate mutation is worse than the error.
167
+ if getattr(exc, '_send_completed', False):
168
+ raise
169
+ # Don't retry timeouts either
166
170
  if "Timeout" in str(exc):
167
171
  raise
168
- # Retry once with a fresh connection for non-timeout errors
172
+ # Send itself failed — safe to retry with a fresh connection
169
173
  self.disconnect()
170
174
  self.connect()
171
175
  response = self._send_raw(command)
172
176
  except OSError:
173
- # Retry once with a fresh connection
177
+ # Socket error before send safe to retry
174
178
  self.disconnect()
175
179
  self.connect()
176
180
  response = self._send_raw(command)
@@ -196,15 +200,6 @@ class AbletonConnection:
196
200
  self._command_log.append(log_entry)
197
201
  return response.get("result", {})
198
202
 
199
- async def send_command_async(self, command_type: str, params: Optional[dict] = None) -> dict:
200
- """Async wrapper around send_command that avoids blocking the event loop.
201
-
202
- Runs the blocking TCP send/receive in a thread pool executor so the
203
- asyncio event loop remains responsive to other concurrent MCP tools.
204
- """
205
- loop = asyncio.get_running_loop()
206
- return await loop.run_in_executor(None, self.send_command, command_type, params)
207
-
208
203
  # ------------------------------------------------------------------
209
204
  # Command log
210
205
  # ------------------------------------------------------------------
@@ -234,7 +229,9 @@ class AbletonConnection:
234
229
  self.disconnect()
235
230
  raise AbletonConnectionError(f"Failed to send command: {exc}") from exc
236
231
 
237
- # Read until newline, preserving any trailing bytes in _recv_buf
232
+ # Read until newline, preserving any trailing bytes in _recv_buf.
233
+ # Any error past this point means the send already reached Ableton,
234
+ # so callers must NOT retry the command (it may have been applied).
238
235
  buf = self._recv_buf
239
236
  try:
240
237
  while b"\n" not in buf:
@@ -242,31 +239,41 @@ class AbletonConnection:
242
239
  if not chunk:
243
240
  self._recv_buf = b""
244
241
  self.disconnect()
245
- raise AbletonConnectionError("Connection closed by Ableton")
242
+ err = AbletonConnectionError("Connection closed by Ableton")
243
+ err._send_completed = True
244
+ raise err
246
245
  buf += chunk
247
246
  if len(buf) > 10 * 1024 * 1024: # 10 MB
248
247
  self._recv_buf = b""
249
248
  self.disconnect()
250
- raise AbletonConnectionError("Response too large (>10 MB)")
249
+ err = AbletonConnectionError("Response too large (>10 MB)")
250
+ err._send_completed = True
251
+ raise err
251
252
  except socket.timeout as exc:
252
253
  self._recv_buf = buf
253
254
  self.disconnect()
254
255
  other_client = _identify_other_tcp_client(self.host, self.port)
255
256
  if other_client:
256
- raise AbletonConnectionError(
257
+ err = AbletonConnectionError(
257
258
  "Timeout waiting for response from Ableton. "
258
259
  f"Another LivePilot client appears to be connected on {self.host}:{self.port} "
259
260
  f"({other_client}). Disconnect the other client and retry."
260
- ) from exc
261
- raise AbletonConnectionError(
261
+ )
262
+ err._send_completed = True
263
+ raise err from exc
264
+ err = AbletonConnectionError(
262
265
  f"Timeout waiting for response from Ableton ({RECV_TIMEOUT}s)"
263
- ) from exc
266
+ )
267
+ err._send_completed = True
268
+ raise err from exc
264
269
  except OSError as exc:
265
270
  self._recv_buf = b""
266
271
  self.disconnect()
267
- raise AbletonConnectionError(
272
+ err = AbletonConnectionError(
268
273
  f"Socket error reading response: {exc}"
269
- ) from exc
274
+ )
275
+ err._send_completed = True
276
+ raise err from exc
270
277
 
271
278
  line, remainder = buf.split(b"\n", 1)
272
279
  self._recv_buf = remainder
@@ -6,7 +6,7 @@ to the appropriate engine-specific evaluator via fabric.evaluate().
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from mcp.server.fastmcp import Context
9
+ from fastmcp import Context
10
10
 
11
11
  from ..server import mcp
12
12
  from ..tools._evaluation_contracts import EvaluationRequest, EvaluationResult
@@ -400,15 +400,18 @@ class M4LBridge:
400
400
  return {"error": "M4L capture timeout — device may be busy or removed"}
401
401
 
402
402
  async def cancel_capture_future(self) -> None:
403
- """Cancel any in-progress capture future (called by capture_stop).
403
+ """Resolve any in-progress capture future with a stopped result.
404
404
 
405
405
  Does NOT acquire _cmd_lock — send_capture holds it during recording.
406
- Cancelling the future causes send_capture's wait_for to raise
407
- CancelledError, which releases the lock naturally.
406
+ Resolving (not cancelling) the future lets send_capture return a
407
+ clean partial-result dict instead of raising CancelledError.
408
408
  """
409
409
  if self.receiver and self.receiver._capture_future \
410
410
  and not self.receiver._capture_future.done():
411
- self.receiver._capture_future.cancel()
411
+ self.receiver._capture_future.set_result({
412
+ "ok": True,
413
+ "stopped_early": True,
414
+ })
412
415
  self.receiver._capture_future = None
413
416
 
414
417
  def _build_osc(self, address: str, args: tuple) -> bytes:
@@ -6,7 +6,7 @@ then delegates to pure-computation modules.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from mcp.server.fastmcp import Context
9
+ from fastmcp import Context
10
10
 
11
11
  from ..server import mcp
12
12
  from ..tools._evaluation_contracts import EvaluationRequest
@@ -6,7 +6,7 @@ then delegates to pure-computation modules.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from mcp.server.fastmcp import Context
9
+ from fastmcp import Context
10
10
 
11
11
  from ..server import mcp
12
12
  from .models import EnergyWindow, SceneRole
@@ -6,7 +6,7 @@ then delegates to pure-computation modules.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from mcp.server.fastmcp import Context
9
+ from fastmcp import Context
10
10
 
11
11
  from ..server import mcp
12
12
  from ..tools._research_engine import get_style_tactics
@@ -6,7 +6,7 @@ then delegates to pure-computation modules.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from mcp.server.fastmcp import Context
9
+ from fastmcp import Context
10
10
 
11
11
  from ..server import mcp
12
12
  from .models import (
@@ -634,14 +634,15 @@ async def capture_audio(
634
634
  async def capture_stop(ctx: Context) -> dict:
635
635
  """Stop an in-progress audio capture early.
636
636
 
637
- Cancels the running buffer~ recording and returns whatever audio has
638
- been captured so far. The partial file is still written to disk.
637
+ Tells the M4L bridge to stop buffer~ recording and resolves the
638
+ in-flight capture_audio call with a partial result (stopped_early=True).
639
+ The partial file is still written to disk by the bridge.
639
640
  Requires LivePilot Analyzer on master track.
640
641
  """
641
642
  cache = _get_spectral(ctx)
642
643
  _require_analyzer(cache)
643
644
  bridge = _get_m4l(ctx)
644
- # Cancel the capture future so send_capture doesn't hang forever
645
+ # Resolve the capture future so send_capture returns cleanly
645
646
  await bridge.cancel_capture_future()
646
647
  return await bridge.send_command("capture_stop")
647
648
 
@@ -176,10 +176,10 @@ def set_group_fold(ctx: Context, track_index: int, folded: bool) -> dict:
176
176
 
177
177
  @mcp.tool()
178
178
  def set_track_input_monitoring(ctx: Context, track_index: int, state: int) -> dict:
179
- """Set input monitoring (0=Off, 1=In, 2=Auto). Only for regular tracks, not return tracks."""
180
- _validate_track_index(track_index)
179
+ """Set input monitoring (0=In, 1=Auto, 2=Off). Only for regular tracks, not return tracks."""
180
+ _validate_track_index(track_index, allow_return=False)
181
181
  if state not in (0, 1, 2):
182
- raise ValueError("Monitoring state must be 0=Off, 1=In, or 2=Auto")
182
+ raise ValueError("Monitoring state must be 0=In, 1=Auto, or 2=Off")
183
183
  return _get_ableton(ctx).send_command("set_track_input_monitoring", {
184
184
  "track_index": track_index,
185
185
  "state": state,
@@ -6,7 +6,7 @@ then delegates to pure-computation critics.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from mcp.server.fastmcp import Context
9
+ from fastmcp import Context
10
10
 
11
11
  from ..server import mcp
12
12
  from .critics import build_translation_report, run_all_translation_critics
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.9.17",
3
+ "version": "1.9.18",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "Agentic production system for Ableton Live 12 — 236 tools, 32 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 — 236 tools, 31 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
6
6
  "author": "Pilot Studio",
7
7
  "license": "MIT",
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.9.17"
8
+ __version__ = "1.9.18"
9
9
 
10
10
  from _Framework.ControlSurface import ControlSurface
11
11
  from .server import LivePilotServer
@@ -190,13 +190,20 @@ def add_arrangement_notes(song, params):
190
190
  try:
191
191
  note_specs = []
192
192
  for note in notes:
193
- spec = Live.Clip.MidiNoteSpecification(
193
+ kwargs = dict(
194
194
  pitch=int(note["pitch"]),
195
195
  start_time=float(note["start_time"]),
196
196
  duration=float(note["duration"]),
197
197
  velocity=float(note.get("velocity", 100)),
198
198
  mute=bool(note.get("mute", False)),
199
199
  )
200
+ if "probability" in note:
201
+ kwargs["probability"] = float(note["probability"])
202
+ if "velocity_deviation" in note:
203
+ kwargs["velocity_deviation"] = float(note["velocity_deviation"])
204
+ if "release_velocity" in note:
205
+ kwargs["release_velocity"] = float(note["release_velocity"])
206
+ spec = Live.Clip.MidiNoteSpecification(**kwargs)
200
207
  note_specs.append(spec)
201
208
  clip.add_new_notes(tuple(note_specs))
202
209
  finally:
@@ -703,4 +710,4 @@ def toggle_cue_point(song, params):
703
710
  def back_to_arranger(song, params):
704
711
  """Switch playback from session clips back to the arrangement timeline."""
705
712
  song.back_to_arranger = True
706
- return {"back_to_arranger": song.back_to_arranger}
713
+ return {"back_to_arranger": True}
@@ -263,6 +263,7 @@ def load_browser_item(song, params):
263
263
  return None
264
264
 
265
265
  for category in categories:
266
+ _iterations[0] = 0 # Reset counter per category to avoid premature cutoff
266
267
  found = find_by_uri(category, uri)
267
268
  if found is not None:
268
269
  song.view.selected_track = track
@@ -100,6 +100,7 @@ def set_clip_automation(song, params):
100
100
  elif parameter_type == "send":
101
101
  if send_index is None:
102
102
  raise ValueError("send_index required for send automation")
103
+ send_index = int(send_index)
103
104
  sends = list(track.mixer_device.sends)
104
105
  if send_index < 0 or send_index >= len(sends):
105
106
  raise IndexError("send_index %d out of range" % send_index)
@@ -107,6 +108,8 @@ def set_clip_automation(song, params):
107
108
  elif parameter_type == "device":
108
109
  if device_index is None or parameter_index is None:
109
110
  raise ValueError("device_index and parameter_index required")
111
+ device_index = int(device_index)
112
+ parameter_index = int(parameter_index)
110
113
  devices = list(track.devices)
111
114
  if device_index < 0 or device_index >= len(devices):
112
115
  raise IndexError("device_index %d out of range" % device_index)
@@ -180,6 +183,7 @@ def clear_clip_automation(song, params):
180
183
  send_index = params.get("send_index")
181
184
  if send_index is None:
182
185
  raise ValueError("send_index required for send automation")
186
+ send_index = int(send_index)
183
187
  sends = list(track.mixer_device.sends)
184
188
  if send_index < 0 or send_index >= len(sends):
185
189
  raise IndexError("send_index %d out of range" % send_index)
@@ -189,6 +193,8 @@ def clear_clip_automation(song, params):
189
193
  parameter_index = params.get("parameter_index")
190
194
  if device_index is None or parameter_index is None:
191
195
  raise ValueError("device_index and parameter_index required")
196
+ device_index = int(device_index)
197
+ parameter_index = int(parameter_index)
192
198
  devices = list(track.devices)
193
199
  if device_index < 0 or device_index >= len(devices):
194
200
  raise IndexError("device_index %d out of range" % device_index)
@@ -72,6 +72,8 @@ def delete_clip(song, params):
72
72
  track_index = int(params["track_index"])
73
73
  clip_index = int(params["clip_index"])
74
74
  clip_slot = get_clip_slot(song, track_index, clip_index)
75
+ if not clip_slot.has_clip:
76
+ raise ValueError("No clip in slot %d on track %d" % (clip_index, track_index))
75
77
  clip_slot.delete_clip()
76
78
  return {"track_index": track_index, "clip_index": clip_index, "deleted": True}
77
79
 
@@ -270,6 +270,7 @@ def load_device_by_uri(song, params):
270
270
  return None
271
271
 
272
272
  for category in categories:
273
+ _iterations[0] = 0 # Reset counter per category to avoid premature cutoff
273
274
  found = find_by_uri(category, uri)
274
275
  if found is not None:
275
276
  song.view.selected_track = track
@@ -54,8 +54,8 @@ def get_track(song, track_index):
54
54
  ri = abs(track_index) - 1
55
55
  if ri >= len(return_tracks):
56
56
  raise IndexError(
57
- "Return track index %d out of range (0..%d)"
58
- % (ri, len(return_tracks) - 1)
57
+ "Return track index %d out of range — %d return tracks available"
58
+ % (ri, len(return_tracks))
59
59
  )
60
60
  return return_tracks[ri]
61
61
  if track_index >= len(tracks):