Add Knowledge Hub books FTS and remove training UI (0.16.0).

Index Assistent/books search.jsonl, expose knowledge hops/catalog in chat, and drop QLoRA/HF training stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-23 23:15:27 +03:00
co-authored by Cursor
parent baa75f2858
commit e1b5743795
25 changed files with 1611 additions and 5024 deletions
+134 -40
View File
@@ -239,6 +239,134 @@ public sealed class AssistentConfig
return map;
}
static List<string> ParseYamlBracketList(string value)
{
value = (value ?? "").Trim();
if (value.StartsWith('[') && value.EndsWith(']'))
{
value = value[1..^1];
}
return value.Split(',')
.Select(s => s.Trim().Trim('"', '\''))
.Where(s => !string.IsNullOrWhiteSpace(s))
.ToList();
}
public string TryFindPackManifestPath(string personaId)
{
string shelf = PackShelfRoot(personaId);
if (string.IsNullOrWhiteSpace(shelf))
{
return null;
}
string dir = shelf;
for (int i = 0; i < 5 && !string.IsNullOrWhiteSpace(dir); i++)
{
string yaml = Path.Combine(dir, "assistent-pack.yaml");
if (File.Exists(yaml))
{
return yaml;
}
dir = Directory.GetParent(dir)?.FullName;
}
return null;
}
/// <summary>Pack manifest knowledge.attach list for a pack persona.</summary>
public List<string> TryParsePackKnowledgeAttach(string personaId)
{
string path = TryFindPackManifestPath(personaId);
if (string.IsNullOrWhiteSpace(path))
{
return [];
}
List<string> items = [];
try
{
foreach (string rawLine in File.ReadAllLines(path, Encoding.UTF8))
{
string line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith('#'))
{
continue;
}
if (line.StartsWith("knowledge.attach:", StringComparison.OrdinalIgnoreCase))
{
string val = line["knowledge.attach:".Length..].Trim();
items.AddRange(ParseYamlBracketList(val));
break;
}
}
}
catch (Exception ex)
{
Logs.Debug($"AssistentConfig pack knowledge.attach {path}: {ex.Message}");
}
return items
.Select(SafeId)
.Where(id => id is not null)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
public JObject LoadKnowledgeAttachOverlay(string personaId)
{
string id = SafeId(personaId);
if (id is null)
{
return null;
}
string path = Path.Combine(_overlayRoot, "personas", id, "knowledge.json");
return File.Exists(path) ? TryReadJson(path) : null;
}
public void SaveKnowledgeAttachOverlay(string personaId, JArray attach)
{
lock (_lock)
{
string id = SafeId(personaId) ?? throw new InvalidOperationException("invalid persona");
string dir = Path.Combine(_overlayRoot, "personas", id);
Directory.CreateDirectory(dir);
string path = Path.Combine(dir, "knowledge.json");
JObject doc = new()
{
["attach"] = attach ?? new JArray(),
};
File.WriteAllText(path, doc.ToString(Newtonsoft.Json.Formatting.Indented) + "\n", Encoding.UTF8);
}
}
static readonly string[] DefaultBundledKnowledgeAttach = ["civitai-krea2", "ru-fictext-rplus"];
static readonly HashSet<string> BundledKnowledgePersonas = new(StringComparer.OrdinalIgnoreCase)
{
"neutral", "aggressive", "dreamer",
};
/// <summary>Effective book attach list for a persona (overlay → pack → bundled defaults).</summary>
public List<string> ResolveKnowledgeAttach(string personaId)
{
string pid = SafeId(personaId) ?? DefaultPersonaId();
JObject overlay = LoadKnowledgeAttachOverlay(pid);
if (overlay?["attach"] is JArray custom && custom.Count > 0)
{
return custom.Select(t => SafeId(t?.ToString()))
.Where(id => id is not null)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
List<string> fromPack = TryParsePackKnowledgeAttach(pid);
if (fromPack.Count > 0)
{
return fromPack;
}
if (IsBundledPersona(pid) && BundledKnowledgePersonas.Contains(pid))
{
return DefaultBundledKnowledgeAttach.ToList();
}
return [];
}
/// <summary>Discover persona packs under Assistent/extensions/*/.</summary>
public List<PackPersona> DiscoverPackPersonas()
{
@@ -477,7 +605,7 @@ public sealed class AssistentConfig
static readonly HashSet<string> ReservedConfigFiles = new(StringComparer.OrdinalIgnoreCase)
{
"exact.json", "controls.json", "skills.json", "ui.json", "assistant.json", "training-qlora.json",
"exact.json", "controls.json", "skills.json", "ui.json", "assistant.json", "knowledge.json",
};
static readonly HashSet<string> ReservedConfigDirs = new(StringComparer.OrdinalIgnoreCase)
@@ -780,7 +908,7 @@ public sealed class AssistentConfig
{
"persona.json", "bio.json", "voice.json", "humor.json", "craft.json",
"appearance.json", "outfits.json", "roleplay.json", "likes.json", "dislikes.json",
"rules.json", "controls.json", "exact.json", "extra.md",
"rules.json", "controls.json", "exact.json", "knowledge.json", "extra.md",
};
public static bool IsWritableShelfName(string fileName)
@@ -934,8 +1062,6 @@ public sealed class AssistentConfig
public JObject LoadUi(string personaId) => MergeJsonLayers("ui.json", LayerRoots(personaId));
public JObject LoadTrainingQlora(string personaId) => MergeJsonLayers("training-qlora.json", LayerRoots(personaId));
/// <summary>Exact (KV) memory: bundled base → persona overlays → disk overlays. Persona keys overwrite base.</summary>
public JObject LoadExact(string personaId) => MergeJsonLayers("exact.json", LayerRoots(personaId));
@@ -1549,40 +1675,6 @@ public sealed class AssistentConfig
}
}
public JObject LoadTrainingRunner()
=> TryReadJson(Path.Combine(_overlayRoot, "training-runner.json")) ?? new JObject();
public void SaveTrainingRunner(JObject settings)
{
lock (_lock)
{
Directory.CreateDirectory(_overlayRoot);
string path = Path.Combine(_overlayRoot, "training-runner.json");
JObject merged = DeepMerge(LoadTrainingRunner(), settings ?? new JObject());
File.WriteAllText(path, merged.ToString(Newtonsoft.Json.Formatting.Indented), Encoding.UTF8);
}
}
public JObject LoadTrainingAgent()
=> TryReadJson(Path.Combine(_overlayRoot, "training-agent.json"))
?? new JObject
{
["enabled"] = true,
["auto_link_on_approve"] = true,
["heard_quota"] = 3,
};
public void SaveTrainingAgent(JObject settings)
{
lock (_lock)
{
Directory.CreateDirectory(_overlayRoot);
string path = Path.Combine(_overlayRoot, "training-agent.json");
JObject merged = DeepMerge(LoadTrainingAgent(), settings ?? new JObject());
File.WriteAllText(path, merged.ToString(Newtonsoft.Json.Formatting.Indented), Encoding.UTF8);
}
}
public JObject LoadOllamaRoles()
{
return TryReadJson(Path.Combine(_overlayRoot, "ollama-roles.json"))
@@ -1643,7 +1735,6 @@ public sealed class AssistentConfig
JObject ui = LoadUi(id);
JObject model = LoadModelProfile(id);
JObject exact = LoadExact(id);
JObject training = LoadTrainingQlora(id);
var packs = ListPacks(id);
var skills = ListSkills(id);
var personas = ListPersonaCatalog();
@@ -1659,7 +1750,6 @@ public sealed class AssistentConfig
["ui"] = ui,
["model"] = model,
["exact"] = exact,
["training"] = training,
["controls"] = controlsSchema,
["control_values"] = controlValues,
["persona_source"] = PersonaSource(id),
@@ -1687,6 +1777,10 @@ public sealed class AssistentConfig
["identity_summary"] = RenderIdentityBlock(id, includeAllShelves: true),
["enabled_skills"] = new JArray(ResolveEnabledSkills(id, null)),
["patch_keys"] = new JArray(LoadPatchKeys()),
["knowledge"] = new JObject
{
["attach"] = new JArray(ResolveKnowledgeAttach(id)),
},
};
}
}