livepilot 1.19.0 → 1.20.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.
- package/CHANGELOG.md +207 -0
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/creative_director/hybrid.py +11 -2
- package/mcp_server/experiment/tools.py +11 -0
- package/mcp_server/memory/session_memory.py +21 -2
- package/mcp_server/memory/tools.py +7 -1
- package/mcp_server/runtime/execution_router.py +9 -0
- package/mcp_server/runtime/mcp_dispatch.py +21 -0
- package/mcp_server/semantic_moves/__init__.py +8 -0
- package/mcp_server/semantic_moves/content_compilers.py +174 -0
- package/mcp_server/semantic_moves/content_moves.py +87 -0
- package/mcp_server/semantic_moves/device_mutation_compilers.py +157 -0
- package/mcp_server/semantic_moves/device_mutation_moves.py +94 -0
- package/mcp_server/semantic_moves/metadata_compilers.py +230 -0
- package/mcp_server/semantic_moves/metadata_moves.py +126 -0
- package/mcp_server/semantic_moves/routing_compilers.py +229 -0
- package/mcp_server/semantic_moves/routing_moves.py +109 -0
- package/mcp_server/semantic_moves/tools.py +57 -3
- package/package.json +1 -1
- package/remote_script/LivePilot/__init__.py +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Compilers for routing-domain semantic moves (v1.20).
|
|
2
|
+
|
|
3
|
+
Each compiler is pure — reads ``kernel["seed_args"]`` for user targets and
|
|
4
|
+
``kernel["session_info"]`` for topology, emits a CompiledPlan of concrete
|
|
5
|
+
tool calls the execution router can dispatch. No I/O, no MCP calls.
|
|
6
|
+
|
|
7
|
+
Rejection policy: when seed_args are missing/invalid, emit a plan with
|
|
8
|
+
``executable=False`` (empty steps) and a clear warning. This is the v1.20
|
|
9
|
+
contract — see docs/plans/v1.20-structural-plan.md §7.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from .compiler import CompiledPlan, CompiledStep, register_compiler
|
|
15
|
+
from .models import SemanticMove
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _return_track_index_to_abs(return_track_index: int) -> int:
|
|
19
|
+
"""Map a 0-based return index to Ableton's negative-track convention.
|
|
20
|
+
|
|
21
|
+
Return A (return_track_index=0) → track_index=-1
|
|
22
|
+
Return B (return_track_index=1) → track_index=-2
|
|
23
|
+
(See mcp_server/tools/mixing.py:227 — "-1=A, -2=B".)
|
|
24
|
+
"""
|
|
25
|
+
return -(return_track_index + 1)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _empty_plan(move: SemanticMove, warnings: list[str]) -> CompiledPlan:
|
|
29
|
+
return CompiledPlan(
|
|
30
|
+
move_id=move.move_id,
|
|
31
|
+
intent=move.intent,
|
|
32
|
+
steps=[],
|
|
33
|
+
risk_level=move.risk_level,
|
|
34
|
+
summary="; ".join(warnings) if warnings else "No plan compiled",
|
|
35
|
+
requires_approval=True,
|
|
36
|
+
warnings=warnings,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ── build_send_chain ──────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _compile_build_send_chain(move: SemanticMove, kernel: dict) -> CompiledPlan:
|
|
44
|
+
args = kernel.get("seed_args") or {}
|
|
45
|
+
return_idx = args.get("return_track_index")
|
|
46
|
+
device_chain = args.get("device_chain")
|
|
47
|
+
warnings: list[str] = []
|
|
48
|
+
|
|
49
|
+
if return_idx is None or device_chain is None:
|
|
50
|
+
return _empty_plan(move, [
|
|
51
|
+
"build_send_chain requires seed_args.return_track_index + device_chain"
|
|
52
|
+
])
|
|
53
|
+
if not isinstance(return_idx, int) or return_idx < 0:
|
|
54
|
+
return _empty_plan(move, [
|
|
55
|
+
f"return_track_index must be a non-negative int, got {return_idx!r}"
|
|
56
|
+
])
|
|
57
|
+
if not isinstance(device_chain, (list, tuple)) or not device_chain:
|
|
58
|
+
return _empty_plan(move, [
|
|
59
|
+
"device_chain is empty — nothing to load onto the return"
|
|
60
|
+
])
|
|
61
|
+
if not all(isinstance(d, str) and d.strip() for d in device_chain):
|
|
62
|
+
return _empty_plan(move, [
|
|
63
|
+
"device_chain entries must be non-empty strings (device names)"
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
abs_track_index = _return_track_index_to_abs(return_idx)
|
|
67
|
+
|
|
68
|
+
steps: list[CompiledStep] = []
|
|
69
|
+
for device_name in device_chain:
|
|
70
|
+
steps.append(CompiledStep(
|
|
71
|
+
tool="find_and_load_device",
|
|
72
|
+
params={
|
|
73
|
+
"track_index": abs_track_index,
|
|
74
|
+
"device_name": device_name,
|
|
75
|
+
# Return chains legitimately may hold two of the same device
|
|
76
|
+
# (Echo feedback stacking, parallel reverbs). Don't block it.
|
|
77
|
+
"allow_duplicate": True,
|
|
78
|
+
},
|
|
79
|
+
description=f"Load {device_name} onto return {chr(ord('A') + return_idx)}",
|
|
80
|
+
verify_after=True,
|
|
81
|
+
backend="remote_command",
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
# Verify-only read at the end (caller uses this to confirm ordering).
|
|
85
|
+
steps.append(CompiledStep(
|
|
86
|
+
tool="get_track_info",
|
|
87
|
+
params={"track_index": abs_track_index},
|
|
88
|
+
description="Verify return-track device order after load",
|
|
89
|
+
verify_after=False,
|
|
90
|
+
backend="remote_command",
|
|
91
|
+
))
|
|
92
|
+
|
|
93
|
+
return CompiledPlan(
|
|
94
|
+
move_id=move.move_id,
|
|
95
|
+
intent=move.intent,
|
|
96
|
+
steps=steps,
|
|
97
|
+
risk_level=move.risk_level,
|
|
98
|
+
summary=(
|
|
99
|
+
f"Load {len(device_chain)} device(s) onto return "
|
|
100
|
+
f"{chr(ord('A') + return_idx)}: {', '.join(device_chain)}"
|
|
101
|
+
),
|
|
102
|
+
requires_approval=(kernel.get("mode", "improve") != "explore"),
|
|
103
|
+
warnings=warnings,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ── configure_send_architecture ───────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _compile_configure_send_architecture(move: SemanticMove, kernel: dict) -> CompiledPlan:
|
|
111
|
+
args = kernel.get("seed_args") or {}
|
|
112
|
+
track_indices = args.get("source_track_indices")
|
|
113
|
+
send_index = args.get("send_index")
|
|
114
|
+
levels = args.get("levels")
|
|
115
|
+
|
|
116
|
+
if track_indices is None or send_index is None or levels is None:
|
|
117
|
+
return _empty_plan(move, [
|
|
118
|
+
"configure_send_architecture requires seed_args.source_track_indices + send_index + levels"
|
|
119
|
+
])
|
|
120
|
+
if not isinstance(track_indices, (list, tuple)) or not track_indices:
|
|
121
|
+
return _empty_plan(move, ["source_track_indices must be a non-empty list"])
|
|
122
|
+
if not isinstance(levels, (list, tuple)):
|
|
123
|
+
return _empty_plan(move, ["levels must be a list"])
|
|
124
|
+
if len(track_indices) != len(levels):
|
|
125
|
+
return _empty_plan(move, [
|
|
126
|
+
f"source_track_indices ({len(track_indices)}) and levels "
|
|
127
|
+
f"({len(levels)}) must have the same length"
|
|
128
|
+
])
|
|
129
|
+
if not isinstance(send_index, int) or send_index < 0:
|
|
130
|
+
return _empty_plan(move, [f"send_index must be a non-negative int, got {send_index!r}"])
|
|
131
|
+
|
|
132
|
+
warnings: list[str] = []
|
|
133
|
+
steps: list[CompiledStep] = []
|
|
134
|
+
for track_i, level in zip(track_indices, levels):
|
|
135
|
+
if not isinstance(track_i, int):
|
|
136
|
+
return _empty_plan(move, [f"track_index must be int, got {track_i!r}"])
|
|
137
|
+
try:
|
|
138
|
+
level_f = float(level)
|
|
139
|
+
except (TypeError, ValueError):
|
|
140
|
+
return _empty_plan(move, [f"level must be numeric, got {level!r}"])
|
|
141
|
+
clamped = max(0.0, min(1.0, level_f))
|
|
142
|
+
if clamped != level_f:
|
|
143
|
+
warnings.append(
|
|
144
|
+
f"Clamped level {level_f} → {clamped} for track {track_i} "
|
|
145
|
+
"(send values must be in [0.0, 1.0])"
|
|
146
|
+
)
|
|
147
|
+
steps.append(CompiledStep(
|
|
148
|
+
tool="set_track_send",
|
|
149
|
+
params={
|
|
150
|
+
"track_index": track_i,
|
|
151
|
+
"send_index": send_index,
|
|
152
|
+
"value": clamped,
|
|
153
|
+
},
|
|
154
|
+
description=(
|
|
155
|
+
f"Set track {track_i} send {send_index} to {clamped:.2f}"
|
|
156
|
+
),
|
|
157
|
+
verify_after=True,
|
|
158
|
+
backend="remote_command",
|
|
159
|
+
))
|
|
160
|
+
|
|
161
|
+
return CompiledPlan(
|
|
162
|
+
move_id=move.move_id,
|
|
163
|
+
intent=move.intent,
|
|
164
|
+
steps=steps,
|
|
165
|
+
risk_level=move.risk_level,
|
|
166
|
+
summary=f"Set {len(steps)} send levels on send {send_index}",
|
|
167
|
+
requires_approval=(kernel.get("mode", "improve") != "explore"),
|
|
168
|
+
warnings=warnings,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# ── set_track_routing ─────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _compile_set_track_routing(move: SemanticMove, kernel: dict) -> CompiledPlan:
|
|
176
|
+
args = kernel.get("seed_args") or {}
|
|
177
|
+
track_index = args.get("track_index")
|
|
178
|
+
# Seed_args keeps the ergonomic ``output_routing_type`` /
|
|
179
|
+
# ``output_routing_channel`` names (matching the MCP tool's public
|
|
180
|
+
# surface, so the director can type them naturally). But compiled
|
|
181
|
+
# steps must use the wire-format names — the remote_command backend
|
|
182
|
+
# bypasses the MCP tool's rename at mcp_server/tools/mixing.py:230
|
|
183
|
+
# (output_routing_type → output_type). Ableton's Remote Script at
|
|
184
|
+
# remote_script/LivePilot/mixing.py:227 keys on the wire format.
|
|
185
|
+
out_type = args.get("output_routing_type")
|
|
186
|
+
out_channel = args.get("output_routing_channel")
|
|
187
|
+
|
|
188
|
+
if track_index is None:
|
|
189
|
+
return _empty_plan(move, ["set_track_routing requires seed_args.track_index"])
|
|
190
|
+
if out_type is None and out_channel is None:
|
|
191
|
+
return _empty_plan(move, [
|
|
192
|
+
"set_track_routing requires at least output_routing_type or output_routing_channel"
|
|
193
|
+
])
|
|
194
|
+
if not isinstance(track_index, int):
|
|
195
|
+
return _empty_plan(move, [f"track_index must be int, got {track_index!r}"])
|
|
196
|
+
|
|
197
|
+
params: dict = {"track_index": track_index}
|
|
198
|
+
if out_type is not None:
|
|
199
|
+
params["output_type"] = str(out_type)
|
|
200
|
+
if out_channel is not None:
|
|
201
|
+
params["output_channel"] = str(out_channel)
|
|
202
|
+
|
|
203
|
+
step = CompiledStep(
|
|
204
|
+
tool="set_track_routing",
|
|
205
|
+
params=params,
|
|
206
|
+
description=(
|
|
207
|
+
f"Set track {track_index} output routing → "
|
|
208
|
+
f"{out_type or '(unchanged)'} / {out_channel or '(unchanged)'}"
|
|
209
|
+
),
|
|
210
|
+
verify_after=True,
|
|
211
|
+
backend="remote_command",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return CompiledPlan(
|
|
215
|
+
move_id=move.move_id,
|
|
216
|
+
intent=move.intent,
|
|
217
|
+
steps=[step],
|
|
218
|
+
risk_level=move.risk_level,
|
|
219
|
+
summary=step.description,
|
|
220
|
+
requires_approval=(kernel.get("mode", "improve") != "explore"),
|
|
221
|
+
warnings=[],
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ── Register compilers ────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
register_compiler("build_send_chain", _compile_build_send_chain)
|
|
228
|
+
register_compiler("configure_send_architecture", _compile_configure_send_architecture)
|
|
229
|
+
register_compiler("set_track_routing", _compile_set_track_routing)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Routing-domain semantic moves (v1.20) — build send chains, configure
|
|
2
|
+
send architectures, rewire track outputs.
|
|
3
|
+
|
|
4
|
+
These moves take user-supplied targets via ``kernel["seed_args"]`` (threaded
|
|
5
|
+
through by ``apply_semantic_move(args=...)`` / ``preview_semantic_move(args=...)``).
|
|
6
|
+
Compilers are pure and live in :mod:`routing_compilers`.
|
|
7
|
+
|
|
8
|
+
The routing family exists to give the Creative Director Phase 6 a
|
|
9
|
+
semantic_move path for three raw-tool patterns that previously required
|
|
10
|
+
the escape hatch:
|
|
11
|
+
|
|
12
|
+
* Loading a chain of devices onto a return track (dub/ambient sends)
|
|
13
|
+
* Setting send levels across a bunch of source tracks in one move
|
|
14
|
+
* Rewiring a track's output routing (e.g., "Sends Only")
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .models import SemanticMove
|
|
18
|
+
from .registry import register
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
BUILD_SEND_CHAIN = SemanticMove(
|
|
22
|
+
move_id="build_send_chain",
|
|
23
|
+
family="device_creation",
|
|
24
|
+
intent=(
|
|
25
|
+
"Build a device chain on a return track — e.g., a dub Echo → "
|
|
26
|
+
"Auto Filter → Convolution Reverb send architecture. Takes the "
|
|
27
|
+
"return_track_index and an ordered device_chain list via seed_args."
|
|
28
|
+
),
|
|
29
|
+
targets={"space": 0.4, "depth": 0.3, "cohesion": 0.3},
|
|
30
|
+
protect={"low_end": 0.6},
|
|
31
|
+
risk_level="medium",
|
|
32
|
+
plan_template=[
|
|
33
|
+
{
|
|
34
|
+
"tool": "find_and_load_device",
|
|
35
|
+
"params": {"description": "Load each device in device_chain onto the return track, in order"},
|
|
36
|
+
"description": "Load device chain onto return",
|
|
37
|
+
"backend": "remote_command",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
verification_plan=[
|
|
41
|
+
{
|
|
42
|
+
"tool": "get_track_info",
|
|
43
|
+
"check": "return track now shows the loaded devices in the expected order",
|
|
44
|
+
"backend": "remote_command",
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
CONFIGURE_SEND_ARCHITECTURE = SemanticMove(
|
|
50
|
+
move_id="configure_send_architecture",
|
|
51
|
+
family="mix",
|
|
52
|
+
intent=(
|
|
53
|
+
"Set send levels across a set of source tracks in a single move — "
|
|
54
|
+
"e.g., route three source tracks to the Reverb return at balanced "
|
|
55
|
+
"levels. Takes source_track_indices, send_index, levels via seed_args."
|
|
56
|
+
),
|
|
57
|
+
targets={"space": 0.5, "depth": 0.3, "cohesion": 0.2},
|
|
58
|
+
protect={"clarity": 0.5},
|
|
59
|
+
risk_level="low",
|
|
60
|
+
plan_template=[
|
|
61
|
+
{
|
|
62
|
+
"tool": "set_track_send",
|
|
63
|
+
"params": {"description": "One set_track_send call per (track, level) pair"},
|
|
64
|
+
"description": "Apply send levels",
|
|
65
|
+
"backend": "remote_command",
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
verification_plan=[
|
|
69
|
+
{
|
|
70
|
+
"tool": "get_track_info",
|
|
71
|
+
"check": "each source track's send value matches the requested level",
|
|
72
|
+
"backend": "remote_command",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
SET_TRACK_ROUTING_MOVE = SemanticMove(
|
|
78
|
+
move_id="set_track_routing",
|
|
79
|
+
family="mix",
|
|
80
|
+
intent=(
|
|
81
|
+
"Rewire a track's output routing — e.g., switch an intermediate "
|
|
82
|
+
"track to 'Sends Only' for a bus architecture. Takes track_index "
|
|
83
|
+
"plus output_routing_type/channel via seed_args."
|
|
84
|
+
),
|
|
85
|
+
# Routing is topology, not a dimension claim; protect clarity because a
|
|
86
|
+
# bad routing move can silence the track entirely.
|
|
87
|
+
targets={},
|
|
88
|
+
protect={"clarity": 0.5},
|
|
89
|
+
risk_level="medium",
|
|
90
|
+
plan_template=[
|
|
91
|
+
{
|
|
92
|
+
"tool": "set_track_routing",
|
|
93
|
+
"params": {"description": "Single set_track_routing call with the provided output fields"},
|
|
94
|
+
"description": "Rewire track output routing",
|
|
95
|
+
"backend": "remote_command",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
verification_plan=[
|
|
99
|
+
{
|
|
100
|
+
"tool": "get_track_routing",
|
|
101
|
+
"check": "output_type and output_channel match the requested values",
|
|
102
|
+
"backend": "remote_command",
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
for _move in (BUILD_SEND_CHAIN, CONFIGURE_SEND_ARCHITECTURE, SET_TRACK_ROUTING_MOVE):
|
|
109
|
+
register(_move)
|
|
@@ -45,6 +45,7 @@ def list_semantic_moves(
|
|
|
45
45
|
def preview_semantic_move(
|
|
46
46
|
ctx: Context,
|
|
47
47
|
move_id: str,
|
|
48
|
+
args: Optional[dict] = None,
|
|
48
49
|
) -> dict:
|
|
49
50
|
"""Preview what a semantic move will do before applying it.
|
|
50
51
|
|
|
@@ -54,6 +55,11 @@ def preview_semantic_move(
|
|
|
54
55
|
tool calls the move would emit right now; use plan_template to understand
|
|
55
56
|
the move's shape independent of session state.
|
|
56
57
|
|
|
58
|
+
args (v1.20+): user-supplied seed parameters threaded into the kernel as
|
|
59
|
+
``kernel["seed_args"]``. Routing / content / metadata moves require these
|
|
60
|
+
(e.g., ``{"return_track_index": 0, "device_chain": ["Echo", ...]}``).
|
|
61
|
+
Pre-v1.20 moves read only from ``session_info`` and ignore seed_args.
|
|
62
|
+
|
|
57
63
|
Existing callers reading plan_template are unaffected by the addition.
|
|
58
64
|
"""
|
|
59
65
|
move = registry.get_move(move_id)
|
|
@@ -96,7 +102,10 @@ def preview_semantic_move(
|
|
|
96
102
|
session_info=session_info,
|
|
97
103
|
capability_state=state.to_dict(),
|
|
98
104
|
)
|
|
99
|
-
|
|
105
|
+
kernel_dict = kernel.to_dict()
|
|
106
|
+
# v1.20: thread user seed_args through to the compiler.
|
|
107
|
+
kernel_dict["seed_args"] = dict(args) if args else {}
|
|
108
|
+
plan = move_compiler.compile(move, kernel_dict)
|
|
100
109
|
result["compiled_plan"] = plan.to_dict()
|
|
101
110
|
result["compiled_plan_executable"] = bool(plan.executable)
|
|
102
111
|
except Exception as e:
|
|
@@ -285,6 +294,7 @@ async def apply_semantic_move(
|
|
|
285
294
|
ctx: Context,
|
|
286
295
|
move_id: str,
|
|
287
296
|
mode: str = "improve",
|
|
297
|
+
args: Optional[dict] = None,
|
|
288
298
|
) -> dict:
|
|
289
299
|
"""Compile and optionally execute a semantic move against the current session.
|
|
290
300
|
|
|
@@ -297,6 +307,12 @@ async def apply_semantic_move(
|
|
|
297
307
|
- "explore": compile and EXECUTE immediately, capturing before/after.
|
|
298
308
|
- "observe" / "diagnose": compile only, never execute. Return the plan.
|
|
299
309
|
|
|
310
|
+
args (v1.20+): user-supplied seed parameters threaded into the kernel as
|
|
311
|
+
``kernel["seed_args"]``. Required by routing / content / metadata moves —
|
|
312
|
+
e.g., ``apply_semantic_move("build_send_chain", mode="explore",
|
|
313
|
+
args={"return_track_index": 0, "device_chain": ["Echo", "Auto Filter"]})``.
|
|
314
|
+
Pre-v1.20 moves read only from ``session_info`` and ignore seed_args.
|
|
315
|
+
|
|
300
316
|
Returns: CompiledPlan with concrete steps, summary, and execution status.
|
|
301
317
|
"""
|
|
302
318
|
from . import compiler
|
|
@@ -312,6 +328,7 @@ async def apply_semantic_move(
|
|
|
312
328
|
"session_info": session_info,
|
|
313
329
|
"mode": mode,
|
|
314
330
|
"capability_state": {},
|
|
331
|
+
"seed_args": dict(args) if args else {},
|
|
315
332
|
}
|
|
316
333
|
|
|
317
334
|
# Compile the move
|
|
@@ -373,9 +390,46 @@ async def apply_semantic_move(
|
|
|
373
390
|
"ok": er.ok,
|
|
374
391
|
})
|
|
375
392
|
|
|
393
|
+
success_count = sum(1 for s in executed_steps if s["ok"])
|
|
394
|
+
failure_count = sum(1 for s in executed_steps if not s["ok"])
|
|
395
|
+
|
|
396
|
+
# v1.20: write the executed move to the SessionLedger so
|
|
397
|
+
# get_last_move / memory_list / anti-repetition-rules can see it
|
|
398
|
+
# WITHOUT requiring the director to call add_session_memory
|
|
399
|
+
# manually. Best-effort — a ledger write failure must not fail
|
|
400
|
+
# the overall move.
|
|
401
|
+
ledger_entry_id: Optional[str] = None
|
|
402
|
+
try:
|
|
403
|
+
from ..runtime.action_ledger import SessionLedger
|
|
404
|
+
ledger = ctx.lifespan_context.setdefault("action_ledger", SessionLedger())
|
|
405
|
+
ledger_entry_id = ledger.start_move(
|
|
406
|
+
engine="semantic_moves",
|
|
407
|
+
move_class=move.family,
|
|
408
|
+
intent=f"{move.move_id}: {move.intent}",
|
|
409
|
+
undo_scope="micro",
|
|
410
|
+
)
|
|
411
|
+
for es in executed_steps:
|
|
412
|
+
if es["ok"]:
|
|
413
|
+
ledger.append_action(
|
|
414
|
+
ledger_entry_id,
|
|
415
|
+
tool_name=es["tool"],
|
|
416
|
+
summary=es.get("description", "") or es["tool"],
|
|
417
|
+
)
|
|
418
|
+
# Provisional keep — evaluate_move / user undo flip this later.
|
|
419
|
+
ledger.finalize_move(
|
|
420
|
+
ledger_entry_id,
|
|
421
|
+
kept=(failure_count == 0),
|
|
422
|
+
score=(float(success_count) / len(executed_steps)) if executed_steps else 0.0,
|
|
423
|
+
memory_candidate=False,
|
|
424
|
+
)
|
|
425
|
+
except Exception as exc: # pragma: no cover — ledger is best-effort
|
|
426
|
+
logger.warning("apply_semantic_move ledger write failed: %s", exc)
|
|
427
|
+
|
|
376
428
|
result = plan.to_dict()
|
|
377
429
|
result["executed"] = True
|
|
378
430
|
result["execution_results"] = executed_steps
|
|
379
|
-
result["success_count"] =
|
|
380
|
-
result["failure_count"] =
|
|
431
|
+
result["success_count"] = success_count
|
|
432
|
+
result["failure_count"] = failure_count
|
|
433
|
+
if ledger_entry_id is not None:
|
|
434
|
+
result["ledger_entry_id"] = ledger_entry_id
|
|
381
435
|
return result
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"mcpName": "io.github.dreamrec/livepilot",
|
|
5
5
|
"description": "Agentic production system for Ableton Live 12 — 429 tools, 53 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",
|
|
@@ -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.
|
|
8
|
+
__version__ = "1.20.0"
|
|
9
9
|
|
|
10
10
|
from _Framework.ControlSurface import ControlSurface
|
|
11
11
|
from . import router
|
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.
|
|
9
|
+
"version": "1.20.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "livepilot",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.20.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|