livepilot 1.10.4 → 1.10.6
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/AGENTS.md +3 -3
- package/CHANGELOG.md +148 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +6 -6
- package/livepilot/.Codex-plugin/plugin.json +2 -2
- package/livepilot/.claude-plugin/plugin.json +2 -2
- package/livepilot/skills/livepilot-core/SKILL.md +4 -4
- package/livepilot/skills/livepilot-core/references/overview.md +3 -3
- package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
- package/livepilot/skills/livepilot-release/SKILL.md +5 -5
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +12 -1
- package/manifest.json +3 -3
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/composer/sample_resolver.py +10 -6
- package/mcp_server/composer/tools.py +10 -6
- package/mcp_server/connection.py +6 -1
- package/mcp_server/creative_constraints/tools.py +9 -8
- package/mcp_server/experiment/engine.py +9 -5
- package/mcp_server/experiment/tools.py +9 -9
- package/mcp_server/hook_hunter/tools.py +14 -9
- package/mcp_server/m4l_bridge.py +11 -0
- package/mcp_server/memory/taste_graph.py +7 -2
- package/mcp_server/mix_engine/tools.py +8 -3
- package/mcp_server/musical_intelligence/tools.py +15 -10
- package/mcp_server/performance_engine/tools.py +6 -2
- package/mcp_server/preview_studio/tools.py +21 -15
- package/mcp_server/project_brain/tools.py +18 -10
- package/mcp_server/reference_engine/tools.py +7 -5
- package/mcp_server/runtime/capability_probe.py +10 -4
- package/mcp_server/runtime/tools.py +8 -2
- package/mcp_server/sample_engine/tools.py +394 -33
- package/mcp_server/semantic_moves/tools.py +5 -1
- package/mcp_server/server.py +10 -9
- package/mcp_server/services/motif_service.py +9 -3
- package/mcp_server/session_continuity/tools.py +7 -3
- package/mcp_server/session_continuity/tracker.py +9 -8
- package/mcp_server/song_brain/tools.py +17 -12
- package/mcp_server/splice_client/client.py +19 -6
- package/mcp_server/stuckness_detector/tools.py +8 -5
- package/mcp_server/tools/_agent_os_engine/__init__.py +52 -0
- package/mcp_server/tools/_agent_os_engine/critics.py +134 -0
- package/mcp_server/tools/_agent_os_engine/evaluation.py +206 -0
- package/mcp_server/tools/_agent_os_engine/models.py +132 -0
- package/mcp_server/tools/_agent_os_engine/taste.py +192 -0
- package/mcp_server/tools/_agent_os_engine/techniques.py +161 -0
- package/mcp_server/tools/_agent_os_engine/world_model.py +170 -0
- package/mcp_server/tools/_composition_engine/__init__.py +67 -0
- package/mcp_server/tools/_composition_engine/analysis.py +174 -0
- package/mcp_server/tools/_composition_engine/critics.py +522 -0
- package/mcp_server/tools/_composition_engine/gestures.py +230 -0
- package/mcp_server/tools/_composition_engine/harmony.py +70 -0
- package/mcp_server/tools/_composition_engine/models.py +193 -0
- package/mcp_server/tools/_composition_engine/sections.py +371 -0
- package/mcp_server/tools/_perception_engine.py +18 -11
- package/mcp_server/tools/agent_os.py +23 -15
- package/mcp_server/tools/analyzer.py +166 -7
- package/mcp_server/tools/automation.py +6 -1
- package/mcp_server/tools/composition.py +25 -16
- package/mcp_server/tools/devices.py +10 -6
- package/mcp_server/tools/motif.py +7 -2
- package/mcp_server/tools/planner.py +6 -2
- package/mcp_server/tools/research.py +13 -10
- package/mcp_server/transition_engine/tools.py +6 -1
- package/mcp_server/translation_engine/tools.py +8 -6
- package/mcp_server/wonder_mode/engine.py +8 -3
- package/mcp_server/wonder_mode/tools.py +29 -21
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/requirements.txt +6 -0
- package/livepilot.mcpb +0 -0
- package/mcp_server/tools/_agent_os_engine.py +0 -947
- package/mcp_server/tools/_composition_engine.py +0 -1530
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Composition engine — structural + musical intelligence for arrangement.
|
|
2
|
+
|
|
3
|
+
This package replaces the former single-file `_composition_engine.py`.
|
|
4
|
+
The public surface is unchanged — callers import the same names:
|
|
5
|
+
|
|
6
|
+
from mcp_server.tools._composition_engine import (
|
|
7
|
+
SectionType, RoleType, GestureIntent,
|
|
8
|
+
build_section_graph_from_scenes, detect_phrases,
|
|
9
|
+
run_form_critic, plan_gesture, ...,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
Internal organization:
|
|
13
|
+
models.py — Enums and dataclasses (shared types)
|
|
14
|
+
sections.py — Section graph, phrase grid, role inference
|
|
15
|
+
critics.py — Form / identity / phrase / transition / emotional / cross-section
|
|
16
|
+
gestures.py — Gesture planner and template library
|
|
17
|
+
harmony.py — Harmony field construction
|
|
18
|
+
analysis.py — Composition evaluation, outcomes, taste model, constants
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from .models import (
|
|
23
|
+
SectionType, RoleType, GestureIntent,
|
|
24
|
+
SectionNode, PhraseUnit, RoleNode, CompositionIssue,
|
|
25
|
+
GesturePlan, CompositionAnalysis, HarmonyField,
|
|
26
|
+
)
|
|
27
|
+
from .sections import (
|
|
28
|
+
build_section_graph_from_scenes,
|
|
29
|
+
build_section_graph_from_arrangement,
|
|
30
|
+
detect_phrases,
|
|
31
|
+
infer_role_for_track,
|
|
32
|
+
build_role_graph,
|
|
33
|
+
)
|
|
34
|
+
from .critics import (
|
|
35
|
+
run_form_critic,
|
|
36
|
+
run_section_identity_critic,
|
|
37
|
+
run_phrase_critic,
|
|
38
|
+
run_transition_critic,
|
|
39
|
+
run_emotional_arc_critic,
|
|
40
|
+
run_cross_section_critic,
|
|
41
|
+
)
|
|
42
|
+
from .gestures import (
|
|
43
|
+
GESTURE_TEMPLATES,
|
|
44
|
+
plan_gesture,
|
|
45
|
+
resolve_gesture_template,
|
|
46
|
+
)
|
|
47
|
+
from .harmony import build_harmony_field
|
|
48
|
+
from .analysis import (
|
|
49
|
+
COMPOSITION_DIMENSIONS,
|
|
50
|
+
analyze_section_outcomes,
|
|
51
|
+
evaluate_composition_move,
|
|
52
|
+
build_composition_taste_model,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"SectionType", "RoleType", "GestureIntent",
|
|
57
|
+
"SectionNode", "PhraseUnit", "RoleNode", "CompositionIssue",
|
|
58
|
+
"GesturePlan", "CompositionAnalysis", "HarmonyField",
|
|
59
|
+
"build_section_graph_from_scenes", "build_section_graph_from_arrangement",
|
|
60
|
+
"detect_phrases", "infer_role_for_track", "build_role_graph",
|
|
61
|
+
"run_form_critic", "run_section_identity_critic", "run_phrase_critic",
|
|
62
|
+
"run_transition_critic", "run_emotional_arc_critic", "run_cross_section_critic",
|
|
63
|
+
"GESTURE_TEMPLATES", "plan_gesture", "resolve_gesture_template",
|
|
64
|
+
"build_harmony_field",
|
|
65
|
+
"COMPOSITION_DIMENSIONS", "analyze_section_outcomes",
|
|
66
|
+
"evaluate_composition_move", "build_composition_taste_model",
|
|
67
|
+
]
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Part of the _composition_engine package — extracted from the single-file engine.
|
|
2
|
+
|
|
3
|
+
Pure-computation core, no external deps. Callers should import from the package
|
|
4
|
+
facade (e.g. `from mcp_server.tools._composition_engine import X`), which
|
|
5
|
+
re-exports everything from these sub-modules.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import math
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import asdict, dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any, Optional
|
|
14
|
+
|
|
15
|
+
from .models import SectionNode, CompositionIssue, CompositionAnalysis
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ── Section Outcome Analysis (Round 2) ────────────────────────────────
|
|
19
|
+
def analyze_section_outcomes(
|
|
20
|
+
outcomes: list[dict],
|
|
21
|
+
) -> dict:
|
|
22
|
+
"""Analyze composition outcomes grouped by section type.
|
|
23
|
+
|
|
24
|
+
outcomes: list of composition_outcome payloads
|
|
25
|
+
Returns: {section_type: {move_name: {avg_score, count, keep_rate}}}
|
|
26
|
+
"""
|
|
27
|
+
by_section: dict[str, list[dict]] = {}
|
|
28
|
+
|
|
29
|
+
for o in outcomes:
|
|
30
|
+
section_type = o.get("section_type", "unknown")
|
|
31
|
+
by_section.setdefault(section_type, []).append(o)
|
|
32
|
+
|
|
33
|
+
result = {}
|
|
34
|
+
for stype, section_outcomes in by_section.items():
|
|
35
|
+
move_stats: dict[str, dict] = {}
|
|
36
|
+
for o in section_outcomes:
|
|
37
|
+
move = o.get("move_name", "unknown")
|
|
38
|
+
stats = move_stats.setdefault(move, {"scores": [], "kept": 0, "total": 0})
|
|
39
|
+
stats["scores"].append(o.get("score", 0))
|
|
40
|
+
stats["total"] += 1
|
|
41
|
+
if o.get("kept", False):
|
|
42
|
+
stats["kept"] += 1
|
|
43
|
+
|
|
44
|
+
result[stype] = {
|
|
45
|
+
move: {
|
|
46
|
+
"avg_score": round(sum(s["scores"]) / len(s["scores"]), 3) if s["scores"] else 0,
|
|
47
|
+
"count": s["total"],
|
|
48
|
+
"keep_rate": round(s["kept"] / s["total"], 3) if s["total"] > 0 else 0,
|
|
49
|
+
}
|
|
50
|
+
for move, s in move_stats.items()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"section_types": list(result.keys()),
|
|
55
|
+
"outcomes_by_section": result,
|
|
56
|
+
"total_outcomes": sum(len(v) for v in by_section.values()),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ── Composition Evaluation ────────────────────────────────────────────
|
|
61
|
+
COMPOSITION_DIMENSIONS = frozenset({
|
|
62
|
+
"section_clarity", "phrase_completion", "narrative_pacing",
|
|
63
|
+
"transition_strength", "orchestration_clarity", "tension_release",
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
def evaluate_composition_move(
|
|
67
|
+
before_issues: list[CompositionIssue],
|
|
68
|
+
after_issues: list[CompositionIssue],
|
|
69
|
+
target_dimensions: dict[str, float],
|
|
70
|
+
protect: dict[str, float],
|
|
71
|
+
) -> dict:
|
|
72
|
+
"""Evaluate whether a composition move improved the arrangement.
|
|
73
|
+
|
|
74
|
+
Compares issue counts and severities before and after.
|
|
75
|
+
Returns: {score, keep_change, issue_delta, notes}
|
|
76
|
+
"""
|
|
77
|
+
notes: list[str] = []
|
|
78
|
+
|
|
79
|
+
# Count issues by type before and after
|
|
80
|
+
before_count = len(before_issues)
|
|
81
|
+
after_count = len(after_issues)
|
|
82
|
+
issue_delta = before_count - after_count
|
|
83
|
+
|
|
84
|
+
# Severity-weighted improvement
|
|
85
|
+
before_severity = sum(i.severity for i in before_issues)
|
|
86
|
+
after_severity = sum(i.severity for i in after_issues)
|
|
87
|
+
severity_improvement = before_severity - after_severity
|
|
88
|
+
|
|
89
|
+
# Score: positive improvement = good
|
|
90
|
+
if before_count > 0:
|
|
91
|
+
improvement_ratio = severity_improvement / max(before_severity, 0.01)
|
|
92
|
+
else:
|
|
93
|
+
improvement_ratio = 0.0 if after_count == 0 else -0.5
|
|
94
|
+
|
|
95
|
+
# Normalize to 0-1 score
|
|
96
|
+
score = max(0.0, min(1.0, 0.5 + improvement_ratio * 0.5))
|
|
97
|
+
|
|
98
|
+
# Keep/undo decision
|
|
99
|
+
keep_change = True
|
|
100
|
+
|
|
101
|
+
if severity_improvement < 0:
|
|
102
|
+
keep_change = False
|
|
103
|
+
notes.append(f"WORSE: total severity increased by {-severity_improvement:.2f}")
|
|
104
|
+
|
|
105
|
+
if after_count > before_count + 1:
|
|
106
|
+
keep_change = False
|
|
107
|
+
notes.append(f"NEW ISSUES: {after_count - before_count} new issues introduced")
|
|
108
|
+
|
|
109
|
+
if score < 0.40:
|
|
110
|
+
keep_change = False
|
|
111
|
+
notes.append(f"SCORE: {score:.3f} below 0.40 threshold")
|
|
112
|
+
|
|
113
|
+
if keep_change and severity_improvement > 0:
|
|
114
|
+
notes.append(f"IMPROVED: resolved {issue_delta} issue(s), severity reduced by {severity_improvement:.2f}")
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"score": round(score, 4),
|
|
118
|
+
"keep_change": keep_change,
|
|
119
|
+
"issue_delta": issue_delta,
|
|
120
|
+
"before_issue_count": before_count,
|
|
121
|
+
"after_issue_count": after_count,
|
|
122
|
+
"severity_improvement": round(severity_improvement, 4),
|
|
123
|
+
"notes": notes,
|
|
124
|
+
"consecutive_undo_hint": not keep_change,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# ── Composition Taste Model (Round 4) ───────────────────────────────
|
|
129
|
+
def build_composition_taste_model(
|
|
130
|
+
section_outcomes: list[dict],
|
|
131
|
+
) -> dict:
|
|
132
|
+
"""Build per-section-type preferences from composition outcome history.
|
|
133
|
+
|
|
134
|
+
Aggregates section outcomes to learn: what density, foreground count,
|
|
135
|
+
and move types does this user prefer for each section type?
|
|
136
|
+
|
|
137
|
+
Returns: {section_type: {preferred_density, preferred_foreground_count,
|
|
138
|
+
top_moves, sample_size}}
|
|
139
|
+
"""
|
|
140
|
+
if not section_outcomes:
|
|
141
|
+
return {"section_types": {}, "sample_size": 0}
|
|
142
|
+
|
|
143
|
+
by_type: dict[str, list[dict]] = {}
|
|
144
|
+
for o in section_outcomes:
|
|
145
|
+
stype = o.get("section_type", "unknown")
|
|
146
|
+
by_type.setdefault(stype, []).append(o)
|
|
147
|
+
|
|
148
|
+
preferences: dict[str, dict] = {}
|
|
149
|
+
for stype, outcomes in by_type.items():
|
|
150
|
+
kept = [o for o in outcomes if o.get("kept", False)]
|
|
151
|
+
densities = [o.get("density", 0.5) for o in kept if "density" in o]
|
|
152
|
+
fg_counts = [o.get("foreground_count", 1) for o in kept if "foreground_count" in o]
|
|
153
|
+
|
|
154
|
+
# Tally move types
|
|
155
|
+
move_counts: dict[str, int] = {}
|
|
156
|
+
for o in kept:
|
|
157
|
+
move = o.get("move_name", "unknown")
|
|
158
|
+
move_counts[move] = move_counts.get(move, 0) + 1
|
|
159
|
+
|
|
160
|
+
top_moves = sorted(move_counts.items(), key=lambda x: -x[1])[:3]
|
|
161
|
+
|
|
162
|
+
preferences[stype] = {
|
|
163
|
+
"preferred_density": round(sum(densities) / len(densities), 2) if densities else 0.5,
|
|
164
|
+
"preferred_foreground_count": round(sum(fg_counts) / len(fg_counts), 1) if fg_counts else 1.0,
|
|
165
|
+
"top_moves": [{"move": m, "count": c} for m, c in top_moves],
|
|
166
|
+
"keep_rate": round(len(kept) / len(outcomes), 3) if outcomes else 0,
|
|
167
|
+
"sample_size": len(outcomes),
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
"section_types": preferences,
|
|
172
|
+
"sample_size": sum(len(v) for v in by_type.values()),
|
|
173
|
+
}
|
|
174
|
+
|