Ship Assistent 0.11.9: esbuild bundle and patch-keys manifest.

Replace split Assets JS with a built bundle and aligned C#/config so SwarmUI loads one script and patch apply stays consistent; drop legacy terse persona and memory seed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-22 13:58:11 +03:00
co-authored by Cursor
parent a5e96f7430
commit e8bb012885
42 changed files with 10652 additions and 1281 deletions
+47 -99
View File
@@ -29,6 +29,10 @@ public sealed class AssistentConfig
public static string SafeId(string id)
{
string s = (id ?? "").Replace('\\', '/').AfterLast('/').Replace("..", "").Trim();
if (string.Equals(s, "terse", StringComparison.OrdinalIgnoreCase))
{
s = "aggressive";
}
if (string.IsNullOrWhiteSpace(s) || !Regex.IsMatch(s, @"^[A-Za-z0-9][A-Za-z0-9_\-]{0,63}$"))
{
return null;
@@ -122,21 +126,29 @@ public sealed class AssistentConfig
}
}
public JArray TryReadJsonArray(string path)
public string[] LoadPatchKeys()
{
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
string path = ResolveUnder(_bundledRoot, "_base/patch-keys.json");
JObject doc = TryReadJson(path);
if (doc?["keys"] is JArray arr && arr.Count > 0)
{
return null;
}
try
{
return JArray.Parse(File.ReadAllText(path, Encoding.UTF8));
}
catch (Exception ex)
{
Logs.Debug($"AssistentConfig json-array {path}: {ex.Message}");
return null;
return arr.Select(t => t?.ToString()).Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
}
// Fallback if bundled json missing
return
[
"prompt", "negative", "loras", "width", "height", "steps", "cfg", "seed", "sigma_shift", "sampler", "scheduler",
"actions", "search_query", "civitai_query",
"use_init_image", "clear_init_image", "init_creativity", "denoise",
"use_mask_image", "clear_mask_image", "mask_blur", "mask_grow",
"look_at", "vision_from", "vision_slots", "slot_to_init", "slot_to_mask",
"snapshot_generate", "select_slot", "aspect", "images", "batch", "vary", "lock_seed",
"creativity", "intensity", "complexity", "movement",
"clear_prompt_images", "slot_to_prompt_image", "pack", "memories", "memory",
"memory_query", "memory_kind", "tag_query", "user_prefs",
"inventory_query", "skills", "persona_shelves", "persona_clone", "persona", "controls",
"variants",
];
}
public string TryReadText(string path)
@@ -272,33 +284,6 @@ public sealed class AssistentConfig
byId[id] = (cur.title, cur.accent, PersonaSource(id));
}
// Legacy personas.json titles
string overlayJson = Path.Combine(_overlayRoot, "personas.json");
JObject legacy = TryReadJson(overlayJson);
if (legacy?["personas"] is JArray arr)
{
foreach (JToken t in arr)
{
if (t is not JObject po)
{
continue;
}
string id = SafeId(po["id"]?.ToString());
if (id is null)
{
continue;
}
if (byId.TryGetValue(id, out var cur))
{
byId[id] = (po["title"]?.ToString() ?? cur.title, cur.accent, cur.source);
}
else
{
byId[id] = (po["title"]?.ToString() ?? id, "#8b949e", "legacy");
}
}
}
return byId.OrderBy(kv => kv.Key, StringComparer.OrdinalIgnoreCase)
.Select(kv => (kv.Key, kv.Value.title, kv.Value.accent, kv.Value.source))
.ToList();
@@ -308,13 +293,6 @@ public sealed class AssistentConfig
{
JObject assistant = MergeJsonLayers("assistant.json", LayerRoots("neutral"));
string def = SafeId(assistant["default_persona"]?.ToString()) ?? "neutral";
string overlayJson = Path.Combine(_overlayRoot, "personas.json");
JObject legacy = TryReadJson(overlayJson);
string fromLegacy = SafeId(legacy?["default"]?.ToString());
if (fromLegacy is not null)
{
def = fromLegacy;
}
var catalog = ListPersonaCatalog();
if (catalog.All(p => !string.Equals(p.id, def, StringComparison.OrdinalIgnoreCase)) && catalog.Count > 0)
{
@@ -410,17 +388,20 @@ public sealed class AssistentConfig
/// <summary>Clamp and merge control values into overlay exact.json (controls key only).</summary>
public JObject SaveControlValues(string personaId, JObject values)
{
string id = SafeId(personaId) ?? "neutral";
JObject schema = LoadControlsSchema(id);
string dir = Path.Combine(_overlayRoot, "personas", id);
Directory.CreateDirectory(dir);
string path = Path.Combine(dir, "exact.json");
JObject existing = TryReadJson(path) ?? new JObject();
JObject prev = existing["controls"] as JObject ?? new JObject();
JObject clamped = ClampControls(schema, values ?? new JObject());
existing["controls"] = DeepMerge(prev, clamped);
File.WriteAllText(path, existing.ToString(Newtonsoft.Json.Formatting.Indented) + "\n", Encoding.UTF8);
return LoadControlValues(id);
lock (_lock)
{
string id = SafeId(personaId) ?? "neutral";
JObject schema = LoadControlsSchema(id);
string dir = Path.Combine(_overlayRoot, "personas", id);
Directory.CreateDirectory(dir);
string path = Path.Combine(dir, "exact.json");
JObject existing = TryReadJson(path) ?? new JObject();
JObject prev = existing["controls"] as JObject ?? new JObject();
JObject clamped = ClampControls(schema, values ?? new JObject());
existing["controls"] = DeepMerge(prev, clamped);
File.WriteAllText(path, existing.ToString(Newtonsoft.Json.Formatting.Indented) + "\n", Encoding.UTF8);
return LoadControlValues(id);
}
}
public static JObject ClampControls(JObject schema, JObject values)
@@ -727,11 +708,6 @@ public sealed class AssistentConfig
{
string id = SafeId(personaId) ?? throw new ArgumentException("invalid persona id");
if (IsBundledPersona(id) && !allowBundledShadow && !IsOverlayPersona(id))
{
// v1: do not shadow-write bundled; require clone to a new overlay id.
throw new InvalidOperationException($"cannot write bundled persona '{id}' — clone to a new overlay id");
}
if (IsBundledPersona(id) && !IsOverlayPersona(id))
{
throw new InvalidOperationException($"cannot write bundled persona '{id}' — clone to a new overlay id");
}
@@ -1099,39 +1075,6 @@ public sealed class AssistentConfig
}
string extra = MergeTextLayers("extra.md", roots);
// Legacy personas.json: only when no overlay persona folder exists for this id.
string overlayPersonaDir = Path.Combine(_overlayRoot, "personas", id);
bool hasOverlayFolder = Directory.Exists(overlayPersonaDir)
&& (File.Exists(Path.Combine(overlayPersonaDir, "persona.json"))
|| File.Exists(Path.Combine(overlayPersonaDir, "extra.md")));
if (!hasOverlayFolder)
{
string overlayJson = Path.Combine(_overlayRoot, "personas.json");
JObject legacy = TryReadJson(overlayJson);
if (legacy?["personas"] is JArray arr)
{
foreach (JToken t in arr)
{
if (t is JObject po && string.Equals(SafeId(po["id"]?.ToString()), id, StringComparison.OrdinalIgnoreCase))
{
JObject persona = shelves["persona"] as JObject ?? new JObject();
string title = po["title"]?.ToString();
if (!string.IsNullOrWhiteSpace(title))
{
persona["title"] = title;
}
shelves["persona"] = persona;
string prompt = po["prompt"]?.ToString();
if (!string.IsNullOrWhiteSpace(prompt))
{
extra = string.IsNullOrWhiteSpace(extra) ? prompt : extra + "\n\n" + prompt;
}
break;
}
}
}
}
shelves["extra"] = extra ?? "";
return shelves;
}
@@ -1428,9 +1371,13 @@ public sealed class AssistentConfig
public void SaveSettings(JObject settings)
{
Directory.CreateDirectory(_overlayRoot);
string path = Path.Combine(_overlayRoot, "settings.json");
File.WriteAllText(path, (settings ?? new JObject()).ToString(Newtonsoft.Json.Formatting.Indented), Encoding.UTF8);
lock (_lock)
{
Directory.CreateDirectory(_overlayRoot);
string path = Path.Combine(_overlayRoot, "settings.json");
JObject merged = DeepMerge(LoadSettings(), settings ?? new JObject());
File.WriteAllText(path, merged.ToString(Newtonsoft.Json.Formatting.Indented), Encoding.UTF8);
}
}
public JObject LoadOllamaRoles()
@@ -1471,7 +1418,7 @@ public sealed class AssistentConfig
}
}
}
if (clientSkills is not null && clientSkills.Count > 0)
if (clientSkills is not null)
{
enabled.Clear();
foreach (JToken t in clientSkills)
@@ -1534,6 +1481,7 @@ public sealed class AssistentConfig
["identity"] = identity,
["identity_summary"] = RenderIdentityBlock(id, includeAllShelves: true),
["enabled_skills"] = new JArray(ResolveEnabledSkills(id, null)),
["patch_keys"] = new JArray(LoadPatchKeys()),
};
}
}