livepilot 1.9.21 → 1.9.23
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/.claude-plugin/marketplace.json +3 -3
- package/.mcpbignore +40 -0
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +47 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +47 -72
- package/bin/livepilot.js +135 -0
- package/livepilot/.Codex-plugin/plugin.json +2 -2
- package/livepilot/.claude-plugin/plugin.json +2 -2
- package/livepilot/agents/livepilot-producer/AGENT.md +13 -0
- package/livepilot/commands/arrange.md +42 -14
- package/livepilot/commands/beat.md +68 -21
- package/livepilot/commands/evaluate.md +23 -13
- package/livepilot/commands/mix.md +35 -11
- package/livepilot/commands/perform.md +31 -19
- package/livepilot/commands/sounddesign.md +38 -17
- package/livepilot/skills/livepilot-arrangement/SKILL.md +2 -1
- package/livepilot/skills/livepilot-composition-engine/references/transition-archetypes.md +2 -2
- package/livepilot/skills/livepilot-core/SKILL.md +60 -4
- package/livepilot/skills/livepilot-core/references/device-atlas/distortion-and-character.md +11 -11
- package/livepilot/skills/livepilot-core/references/device-atlas/drums-and-percussion.md +25 -25
- package/livepilot/skills/livepilot-core/references/device-atlas/dynamics-and-punch.md +21 -21
- package/livepilot/skills/livepilot-core/references/device-atlas/eq-and-filtering.md +13 -13
- package/livepilot/skills/livepilot-core/references/device-atlas/midi-tools.md +13 -13
- package/livepilot/skills/livepilot-core/references/device-atlas/movement-and-modulation.md +5 -5
- package/livepilot/skills/livepilot-core/references/device-atlas/space-and-depth.md +16 -16
- package/livepilot/skills/livepilot-core/references/device-atlas/spectral-and-weird.md +40 -40
- package/livepilot/skills/livepilot-core/references/m4l-devices.md +3 -3
- package/livepilot/skills/livepilot-core/references/overview.md +4 -4
- package/livepilot/skills/livepilot-evaluation/SKILL.md +12 -8
- package/livepilot/skills/livepilot-evaluation/references/memory-promotion.md +2 -2
- package/livepilot/skills/livepilot-mix-engine/SKILL.md +1 -1
- package/livepilot/skills/livepilot-mix-engine/references/mix-moves.md +2 -2
- package/livepilot/skills/livepilot-mixing/SKILL.md +3 -1
- package/livepilot/skills/livepilot-notes/SKILL.md +2 -1
- package/livepilot/skills/livepilot-release/SKILL.md +15 -15
- package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +2 -2
- package/livepilot/skills/livepilot-wonder/SKILL.md +62 -0
- package/livepilot.mcpb +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/manifest.json +91 -0
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/creative_constraints/__init__.py +6 -0
- package/mcp_server/creative_constraints/engine.py +277 -0
- package/mcp_server/creative_constraints/models.py +75 -0
- package/mcp_server/creative_constraints/tools.py +341 -0
- package/mcp_server/experiment/__init__.py +6 -0
- package/mcp_server/experiment/engine.py +213 -0
- package/mcp_server/experiment/models.py +120 -0
- package/mcp_server/experiment/tools.py +263 -0
- package/mcp_server/hook_hunter/__init__.py +5 -0
- package/mcp_server/hook_hunter/analyzer.py +342 -0
- package/mcp_server/hook_hunter/models.py +57 -0
- package/mcp_server/hook_hunter/tools.py +586 -0
- package/mcp_server/memory/taste_graph.py +261 -0
- package/mcp_server/memory/tools.py +88 -0
- package/mcp_server/mix_engine/critics.py +2 -2
- package/mcp_server/mix_engine/models.py +1 -1
- package/mcp_server/mix_engine/state_builder.py +2 -2
- package/mcp_server/musical_intelligence/__init__.py +8 -0
- package/mcp_server/musical_intelligence/detectors.py +421 -0
- package/mcp_server/musical_intelligence/phrase_critic.py +163 -0
- package/mcp_server/musical_intelligence/tools.py +221 -0
- package/mcp_server/preview_studio/__init__.py +5 -0
- package/mcp_server/preview_studio/engine.py +280 -0
- package/mcp_server/preview_studio/models.py +73 -0
- package/mcp_server/preview_studio/tools.py +423 -0
- package/mcp_server/runtime/session_kernel.py +96 -0
- package/mcp_server/runtime/tools.py +90 -1
- package/mcp_server/semantic_moves/__init__.py +13 -0
- package/mcp_server/semantic_moves/compiler.py +116 -0
- package/mcp_server/semantic_moves/mix_compilers.py +291 -0
- package/mcp_server/semantic_moves/mix_moves.py +157 -0
- package/mcp_server/semantic_moves/models.py +46 -0
- package/mcp_server/semantic_moves/performance_compilers.py +208 -0
- package/mcp_server/semantic_moves/performance_moves.py +81 -0
- package/mcp_server/semantic_moves/registry.py +32 -0
- package/mcp_server/semantic_moves/resolvers.py +126 -0
- package/mcp_server/semantic_moves/sound_design_compilers.py +266 -0
- package/mcp_server/semantic_moves/sound_design_moves.py +78 -0
- package/mcp_server/semantic_moves/tools.py +204 -0
- package/mcp_server/semantic_moves/transition_compilers.py +222 -0
- package/mcp_server/semantic_moves/transition_moves.py +76 -0
- package/mcp_server/server.py +10 -0
- package/mcp_server/session_continuity/__init__.py +6 -0
- package/mcp_server/session_continuity/models.py +86 -0
- package/mcp_server/session_continuity/tools.py +230 -0
- package/mcp_server/session_continuity/tracker.py +235 -0
- package/mcp_server/song_brain/__init__.py +6 -0
- package/mcp_server/song_brain/builder.py +477 -0
- package/mcp_server/song_brain/models.py +132 -0
- package/mcp_server/song_brain/tools.py +294 -0
- package/mcp_server/stuckness_detector/__init__.py +5 -0
- package/mcp_server/stuckness_detector/detector.py +400 -0
- package/mcp_server/stuckness_detector/models.py +66 -0
- package/mcp_server/stuckness_detector/tools.py +195 -0
- package/mcp_server/tools/_conductor.py +104 -6
- package/mcp_server/tools/analyzer.py +1 -1
- package/mcp_server/tools/devices.py +34 -0
- package/mcp_server/wonder_mode/__init__.py +6 -0
- package/mcp_server/wonder_mode/diagnosis.py +84 -0
- package/mcp_server/wonder_mode/engine.py +493 -0
- package/mcp_server/wonder_mode/session.py +114 -0
- package/mcp_server/wonder_mode/tools.py +285 -0
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/remote_script/LivePilot/browser.py +4 -1
- package/remote_script/LivePilot/devices.py +29 -0
- package/remote_script/LivePilot/tracks.py +11 -4
- package/scripts/generate_tool_catalog.py +131 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Session Continuity tracker — pure computation + in-memory state.
|
|
2
|
+
|
|
3
|
+
Manages creative threads, turn resolutions, and session story.
|
|
4
|
+
Separates taste (cross-session) from identity (in-song) ranking.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import time
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from .models import (
|
|
14
|
+
CreativeThread,
|
|
15
|
+
SessionStory,
|
|
16
|
+
TasteIdentityRanking,
|
|
17
|
+
TurnResolution,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ── In-memory state ───────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
_story = SessionStory()
|
|
24
|
+
_threads: dict[str, CreativeThread] = {}
|
|
25
|
+
_turns: list[TurnResolution] = []
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def reset_story() -> None:
|
|
29
|
+
"""Reset session story (for testing)."""
|
|
30
|
+
global _story, _threads, _turns
|
|
31
|
+
_story = SessionStory()
|
|
32
|
+
_threads = {}
|
|
33
|
+
_turns = []
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── Session story ─────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_session_story(
|
|
40
|
+
song_brain: Optional[dict] = None,
|
|
41
|
+
) -> SessionStory:
|
|
42
|
+
"""Get the current session story with identity summary."""
|
|
43
|
+
song_brain = song_brain or {}
|
|
44
|
+
|
|
45
|
+
_story.identity_summary = song_brain.get("identity_core", "")
|
|
46
|
+
_story.threads = [t for t in _threads.values() if t.status == "open"]
|
|
47
|
+
_story.turns = _turns
|
|
48
|
+
_story.what_still_feels_open = [
|
|
49
|
+
t.description for t in _threads.values()
|
|
50
|
+
if t.status == "open" and not t.is_stale
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
if _turns:
|
|
54
|
+
last = _turns[-1]
|
|
55
|
+
_story.what_changed_last = f"{last.request_text} → {last.outcome}"
|
|
56
|
+
|
|
57
|
+
return _story
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def resume_last_intent() -> dict:
|
|
61
|
+
"""Resume the most recent unresolved creative intent."""
|
|
62
|
+
open_threads = [
|
|
63
|
+
t for t in _threads.values()
|
|
64
|
+
if t.status == "open" and not t.is_stale
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
if not open_threads:
|
|
68
|
+
return {
|
|
69
|
+
"found": False,
|
|
70
|
+
"note": "No unresolved creative intents to resume",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Sort by last touched (most recent first)
|
|
74
|
+
open_threads.sort(key=lambda t: t.last_touched_ms, reverse=True)
|
|
75
|
+
latest = open_threads[0]
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"found": True,
|
|
79
|
+
"thread_id": latest.thread_id,
|
|
80
|
+
"description": latest.description,
|
|
81
|
+
"domain": latest.domain,
|
|
82
|
+
"priority": latest.priority,
|
|
83
|
+
"suggestion": f"Continue working on: {latest.description}",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ── Turn tracking ─────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def record_turn_resolution(
|
|
91
|
+
request_text: str,
|
|
92
|
+
outcome: str = "accepted",
|
|
93
|
+
move_applied: str = "",
|
|
94
|
+
identity_effect: str = "",
|
|
95
|
+
user_sentiment: str = "neutral",
|
|
96
|
+
) -> TurnResolution:
|
|
97
|
+
"""Record what happened in a creative turn."""
|
|
98
|
+
now = int(time.time() * 1000)
|
|
99
|
+
turn_id = hashlib.sha256(f"{request_text}_{now}".encode()).hexdigest()[:10]
|
|
100
|
+
|
|
101
|
+
turn = TurnResolution(
|
|
102
|
+
turn_id=turn_id,
|
|
103
|
+
request_text=request_text,
|
|
104
|
+
outcome=outcome,
|
|
105
|
+
move_applied=move_applied,
|
|
106
|
+
identity_effect=identity_effect,
|
|
107
|
+
user_sentiment=user_sentiment,
|
|
108
|
+
timestamp_ms=now,
|
|
109
|
+
)
|
|
110
|
+
_turns.append(turn)
|
|
111
|
+
|
|
112
|
+
# Update mood arc
|
|
113
|
+
if user_sentiment in ("loved", "liked"):
|
|
114
|
+
_story.mood_arc.append("positive")
|
|
115
|
+
elif user_sentiment in ("disliked", "hated"):
|
|
116
|
+
_story.mood_arc.append("negative")
|
|
117
|
+
else:
|
|
118
|
+
_story.mood_arc.append("neutral")
|
|
119
|
+
|
|
120
|
+
return turn
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ── Creative threads ──────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def open_thread(description: str, domain: str = "", priority: float = 0.5) -> CreativeThread:
|
|
127
|
+
"""Open a new creative thread."""
|
|
128
|
+
now = int(time.time() * 1000)
|
|
129
|
+
thread_id = hashlib.sha256(f"{description}_{now}".encode()).hexdigest()[:10]
|
|
130
|
+
|
|
131
|
+
thread = CreativeThread(
|
|
132
|
+
thread_id=thread_id,
|
|
133
|
+
description=description,
|
|
134
|
+
domain=domain,
|
|
135
|
+
status="open",
|
|
136
|
+
priority=priority,
|
|
137
|
+
created_at_ms=now,
|
|
138
|
+
last_touched_ms=now,
|
|
139
|
+
)
|
|
140
|
+
_threads[thread_id] = thread
|
|
141
|
+
return thread
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def resolve_thread(thread_id: str) -> Optional[CreativeThread]:
|
|
145
|
+
"""Mark a creative thread as resolved."""
|
|
146
|
+
thread = _threads.get(thread_id)
|
|
147
|
+
if thread:
|
|
148
|
+
thread.status = "resolved"
|
|
149
|
+
thread.last_touched_ms = int(time.time() * 1000)
|
|
150
|
+
return thread
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def list_open_threads() -> list[CreativeThread]:
|
|
154
|
+
"""List all open (non-stale) creative threads."""
|
|
155
|
+
return [
|
|
156
|
+
t for t in _threads.values()
|
|
157
|
+
if t.status == "open" and not t.is_stale
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ── Taste vs Identity ranking ────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def rank_by_taste_and_identity(
|
|
165
|
+
candidates: list[dict],
|
|
166
|
+
taste_graph: Optional[dict] = None,
|
|
167
|
+
song_brain: Optional[dict] = None,
|
|
168
|
+
) -> list[TasteIdentityRanking]:
|
|
169
|
+
"""Rank candidates with separated taste and identity scoring.
|
|
170
|
+
|
|
171
|
+
Taste ranks options (cross-session preference).
|
|
172
|
+
Identity constrains/shapes options (in-song).
|
|
173
|
+
Identity has stronger weight inside a session.
|
|
174
|
+
"""
|
|
175
|
+
taste_graph = taste_graph or {}
|
|
176
|
+
song_brain = song_brain or {}
|
|
177
|
+
results: list[TasteIdentityRanking] = []
|
|
178
|
+
|
|
179
|
+
for candidate in candidates:
|
|
180
|
+
cid = candidate.get("id", candidate.get("variant_id", ""))
|
|
181
|
+
novelty = candidate.get("novelty_level", 0.5)
|
|
182
|
+
identity_effect = candidate.get("identity_effect", "preserves")
|
|
183
|
+
|
|
184
|
+
# Taste score — how well does this fit cross-session preferences?
|
|
185
|
+
boldness_pref = taste_graph.get("transition_boldness", 0.5)
|
|
186
|
+
taste_score = 1.0 - abs(novelty - boldness_pref) * 0.8
|
|
187
|
+
taste_score = round(max(0.0, min(1.0, taste_score)), 3)
|
|
188
|
+
|
|
189
|
+
# Identity score — does this serve the current song?
|
|
190
|
+
identity_scores = {
|
|
191
|
+
"preserves": 0.9,
|
|
192
|
+
"evolves": 0.7,
|
|
193
|
+
"contrasts": 0.45,
|
|
194
|
+
"resets": 0.15,
|
|
195
|
+
}
|
|
196
|
+
identity_score = identity_scores.get(identity_effect, 0.5)
|
|
197
|
+
|
|
198
|
+
# Sacred element penalty — penalize non-preserving candidates
|
|
199
|
+
# that target sacred dimensions
|
|
200
|
+
sacred = song_brain.get("sacred_elements", [])
|
|
201
|
+
targets = candidate.get("targets_snapshot", {})
|
|
202
|
+
sacred_penalty = sum(
|
|
203
|
+
s.get("salience", 0.5) * 0.15
|
|
204
|
+
for s in sacred
|
|
205
|
+
if s.get("element_type") in targets and identity_effect != "preserves"
|
|
206
|
+
)
|
|
207
|
+
identity_score = max(0.0, identity_score - sacred_penalty)
|
|
208
|
+
|
|
209
|
+
# Composite: identity weighted more heavily within a session
|
|
210
|
+
composite = taste_score * 0.35 + identity_score * 0.65
|
|
211
|
+
|
|
212
|
+
# Explanations
|
|
213
|
+
taste_exp = (
|
|
214
|
+
f"{'Good' if taste_score > 0.6 else 'Moderate' if taste_score > 0.3 else 'Poor'} "
|
|
215
|
+
f"taste fit — novelty {novelty:.0%} vs preference {boldness_pref:.0%}"
|
|
216
|
+
)
|
|
217
|
+
identity_exp = (
|
|
218
|
+
f"Identity effect: {identity_effect} — "
|
|
219
|
+
f"{'safe for current song' if identity_score > 0.6 else 'risky for song identity'}"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
results.append(TasteIdentityRanking(
|
|
223
|
+
candidate_id=cid,
|
|
224
|
+
taste_score=taste_score,
|
|
225
|
+
identity_score=identity_score,
|
|
226
|
+
composite_score=round(composite, 3),
|
|
227
|
+
taste_explanation=taste_exp,
|
|
228
|
+
identity_explanation=identity_exp,
|
|
229
|
+
recommendation="recommended" if composite > 0.6 else (
|
|
230
|
+
"consider" if composite > 0.4 else "caution"
|
|
231
|
+
),
|
|
232
|
+
))
|
|
233
|
+
|
|
234
|
+
results.sort(key=lambda r: r.composite_score, reverse=True)
|
|
235
|
+
return results
|