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.
Files changed (110) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.mcpbignore +40 -0
  3. package/AGENTS.md +2 -2
  4. package/CHANGELOG.md +47 -0
  5. package/CONTRIBUTING.md +1 -1
  6. package/README.md +47 -72
  7. package/bin/livepilot.js +135 -0
  8. package/livepilot/.Codex-plugin/plugin.json +2 -2
  9. package/livepilot/.claude-plugin/plugin.json +2 -2
  10. package/livepilot/agents/livepilot-producer/AGENT.md +13 -0
  11. package/livepilot/commands/arrange.md +42 -14
  12. package/livepilot/commands/beat.md +68 -21
  13. package/livepilot/commands/evaluate.md +23 -13
  14. package/livepilot/commands/mix.md +35 -11
  15. package/livepilot/commands/perform.md +31 -19
  16. package/livepilot/commands/sounddesign.md +38 -17
  17. package/livepilot/skills/livepilot-arrangement/SKILL.md +2 -1
  18. package/livepilot/skills/livepilot-composition-engine/references/transition-archetypes.md +2 -2
  19. package/livepilot/skills/livepilot-core/SKILL.md +60 -4
  20. package/livepilot/skills/livepilot-core/references/device-atlas/distortion-and-character.md +11 -11
  21. package/livepilot/skills/livepilot-core/references/device-atlas/drums-and-percussion.md +25 -25
  22. package/livepilot/skills/livepilot-core/references/device-atlas/dynamics-and-punch.md +21 -21
  23. package/livepilot/skills/livepilot-core/references/device-atlas/eq-and-filtering.md +13 -13
  24. package/livepilot/skills/livepilot-core/references/device-atlas/midi-tools.md +13 -13
  25. package/livepilot/skills/livepilot-core/references/device-atlas/movement-and-modulation.md +5 -5
  26. package/livepilot/skills/livepilot-core/references/device-atlas/space-and-depth.md +16 -16
  27. package/livepilot/skills/livepilot-core/references/device-atlas/spectral-and-weird.md +40 -40
  28. package/livepilot/skills/livepilot-core/references/m4l-devices.md +3 -3
  29. package/livepilot/skills/livepilot-core/references/overview.md +4 -4
  30. package/livepilot/skills/livepilot-evaluation/SKILL.md +12 -8
  31. package/livepilot/skills/livepilot-evaluation/references/memory-promotion.md +2 -2
  32. package/livepilot/skills/livepilot-mix-engine/SKILL.md +1 -1
  33. package/livepilot/skills/livepilot-mix-engine/references/mix-moves.md +2 -2
  34. package/livepilot/skills/livepilot-mixing/SKILL.md +3 -1
  35. package/livepilot/skills/livepilot-notes/SKILL.md +2 -1
  36. package/livepilot/skills/livepilot-release/SKILL.md +15 -15
  37. package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +2 -2
  38. package/livepilot/skills/livepilot-wonder/SKILL.md +62 -0
  39. package/livepilot.mcpb +0 -0
  40. package/m4l_device/livepilot_bridge.js +1 -1
  41. package/manifest.json +91 -0
  42. package/mcp_server/__init__.py +1 -1
  43. package/mcp_server/creative_constraints/__init__.py +6 -0
  44. package/mcp_server/creative_constraints/engine.py +277 -0
  45. package/mcp_server/creative_constraints/models.py +75 -0
  46. package/mcp_server/creative_constraints/tools.py +341 -0
  47. package/mcp_server/experiment/__init__.py +6 -0
  48. package/mcp_server/experiment/engine.py +213 -0
  49. package/mcp_server/experiment/models.py +120 -0
  50. package/mcp_server/experiment/tools.py +263 -0
  51. package/mcp_server/hook_hunter/__init__.py +5 -0
  52. package/mcp_server/hook_hunter/analyzer.py +342 -0
  53. package/mcp_server/hook_hunter/models.py +57 -0
  54. package/mcp_server/hook_hunter/tools.py +586 -0
  55. package/mcp_server/memory/taste_graph.py +261 -0
  56. package/mcp_server/memory/tools.py +88 -0
  57. package/mcp_server/mix_engine/critics.py +2 -2
  58. package/mcp_server/mix_engine/models.py +1 -1
  59. package/mcp_server/mix_engine/state_builder.py +2 -2
  60. package/mcp_server/musical_intelligence/__init__.py +8 -0
  61. package/mcp_server/musical_intelligence/detectors.py +421 -0
  62. package/mcp_server/musical_intelligence/phrase_critic.py +163 -0
  63. package/mcp_server/musical_intelligence/tools.py +221 -0
  64. package/mcp_server/preview_studio/__init__.py +5 -0
  65. package/mcp_server/preview_studio/engine.py +280 -0
  66. package/mcp_server/preview_studio/models.py +73 -0
  67. package/mcp_server/preview_studio/tools.py +423 -0
  68. package/mcp_server/runtime/session_kernel.py +96 -0
  69. package/mcp_server/runtime/tools.py +90 -1
  70. package/mcp_server/semantic_moves/__init__.py +13 -0
  71. package/mcp_server/semantic_moves/compiler.py +116 -0
  72. package/mcp_server/semantic_moves/mix_compilers.py +291 -0
  73. package/mcp_server/semantic_moves/mix_moves.py +157 -0
  74. package/mcp_server/semantic_moves/models.py +46 -0
  75. package/mcp_server/semantic_moves/performance_compilers.py +208 -0
  76. package/mcp_server/semantic_moves/performance_moves.py +81 -0
  77. package/mcp_server/semantic_moves/registry.py +32 -0
  78. package/mcp_server/semantic_moves/resolvers.py +126 -0
  79. package/mcp_server/semantic_moves/sound_design_compilers.py +266 -0
  80. package/mcp_server/semantic_moves/sound_design_moves.py +78 -0
  81. package/mcp_server/semantic_moves/tools.py +204 -0
  82. package/mcp_server/semantic_moves/transition_compilers.py +222 -0
  83. package/mcp_server/semantic_moves/transition_moves.py +76 -0
  84. package/mcp_server/server.py +10 -0
  85. package/mcp_server/session_continuity/__init__.py +6 -0
  86. package/mcp_server/session_continuity/models.py +86 -0
  87. package/mcp_server/session_continuity/tools.py +230 -0
  88. package/mcp_server/session_continuity/tracker.py +235 -0
  89. package/mcp_server/song_brain/__init__.py +6 -0
  90. package/mcp_server/song_brain/builder.py +477 -0
  91. package/mcp_server/song_brain/models.py +132 -0
  92. package/mcp_server/song_brain/tools.py +294 -0
  93. package/mcp_server/stuckness_detector/__init__.py +5 -0
  94. package/mcp_server/stuckness_detector/detector.py +400 -0
  95. package/mcp_server/stuckness_detector/models.py +66 -0
  96. package/mcp_server/stuckness_detector/tools.py +195 -0
  97. package/mcp_server/tools/_conductor.py +104 -6
  98. package/mcp_server/tools/analyzer.py +1 -1
  99. package/mcp_server/tools/devices.py +34 -0
  100. package/mcp_server/wonder_mode/__init__.py +6 -0
  101. package/mcp_server/wonder_mode/diagnosis.py +84 -0
  102. package/mcp_server/wonder_mode/engine.py +493 -0
  103. package/mcp_server/wonder_mode/session.py +114 -0
  104. package/mcp_server/wonder_mode/tools.py +285 -0
  105. package/package.json +2 -2
  106. package/remote_script/LivePilot/__init__.py +1 -1
  107. package/remote_script/LivePilot/browser.py +4 -1
  108. package/remote_script/LivePilot/devices.py +29 -0
  109. package/remote_script/LivePilot/tracks.py +11 -4
  110. 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
@@ -0,0 +1,6 @@
1
+ """SongBrain — song identity modeling for Stage 2 Magic Layer.
2
+
3
+ Captures the musical identity of the current piece:
4
+ identity core, sacred elements, section purposes, energy arc,
5
+ and payoff targets. Distinct from cross-session user taste.
6
+ """