Ship Assistent 0.8.1: split modules and shared+personal vector memory.
Personal RAG never leaks into the shared store; retrieve merges shared plus the persona chain, with personal overwrite on kind+key. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+64
-33
@@ -189,7 +189,8 @@ public sealed class AssistentConfig
|
||||
return last;
|
||||
}
|
||||
|
||||
List<string> PersonaExtendsChain(string personaId)
|
||||
/// <summary>Ancestor-first chain ending with the current persona (for overlay merge and vector retrieve).</summary>
|
||||
public List<string> PersonaExtendsChain(string personaId)
|
||||
{
|
||||
List<string> chain = [];
|
||||
HashSet<string> seen = new(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -498,27 +499,35 @@ public sealed class AssistentConfig
|
||||
JObject rules = MergeJsonLayers("rules.json", roots);
|
||||
string extra = MergeTextLayers("extra.md", roots);
|
||||
|
||||
// Legacy personas.json prompt → extra overlay
|
||||
string overlayJson = Path.Combine(_overlayRoot, "personas.json");
|
||||
JObject legacy = TryReadJson(overlayJson);
|
||||
if (legacy?["personas"] is JArray arr)
|
||||
// Legacy personas.json: only when no overlay persona folder exists for this id
|
||||
// (gpu-rent now seeds personas/<id>/extra.md instead of dumping personas.json).
|
||||
string id = SafeId(personaId) ?? "neutral";
|
||||
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 id = SafeId(personaId) ?? "neutral";
|
||||
foreach (JToken t in arr)
|
||||
string overlayJson = Path.Combine(_overlayRoot, "personas.json");
|
||||
JObject legacy = TryReadJson(overlayJson);
|
||||
if (legacy?["personas"] is JArray arr)
|
||||
{
|
||||
if (t is JObject po && string.Equals(SafeId(po["id"]?.ToString()), id, StringComparison.OrdinalIgnoreCase))
|
||||
foreach (JToken t in arr)
|
||||
{
|
||||
string title = po["title"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
if (t is JObject po && string.Equals(SafeId(po["id"]?.ToString()), id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
persona["title"] = title;
|
||||
string title = po["title"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
persona["title"] = title;
|
||||
}
|
||||
string prompt = po["prompt"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
extra = string.IsNullOrWhiteSpace(extra) ? prompt : extra + "\n\n" + prompt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
string prompt = po["prompt"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
extra = string.IsNullOrWhiteSpace(extra) ? prompt : extra + "\n\n" + prompt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -641,35 +650,51 @@ public sealed class AssistentConfig
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
static IEnumerable<string> PersonaIdsUnder(string root)
|
||||
{
|
||||
string dir = Path.Combine(root ?? "", "personas");
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
foreach (string folder in Directory.GetDirectories(dir))
|
||||
{
|
||||
string id = SafeId(Path.GetFileName(folder));
|
||||
if (id is not null)
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Seed docs: shared from _base/memory-seed, personal from personas/<id>/memory-seed. Later files overwrite earlier same kind+key in the list; persona is stamped on each doc.</summary>
|
||||
public List<JObject> LoadMemorySeedDocs()
|
||||
{
|
||||
List<JObject> docs = [];
|
||||
void Scan(string root)
|
||||
void Scan(string dir, string persona)
|
||||
{
|
||||
string dir = Path.Combine(root, "memory-seed");
|
||||
if (!Directory.Exists(dir))
|
||||
if (string.IsNullOrWhiteSpace(dir) || !Directory.Exists(dir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string stamp = AssistentMemory.NormalizePersona(persona);
|
||||
foreach (string file in Directory.GetFiles(dir, "*.json").OrderBy(f => f, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
string raw = File.ReadAllText(file, Encoding.UTF8);
|
||||
JToken parsed = JToken.Parse(raw);
|
||||
if (parsed is JArray arr)
|
||||
IEnumerable<JObject> items = parsed is JArray arr
|
||||
? arr.OfType<JObject>()
|
||||
: parsed is JObject single ? new[] { single } : [];
|
||||
foreach (JObject jo in items)
|
||||
{
|
||||
foreach (JToken t in arr)
|
||||
JObject clone = (JObject)jo.DeepClone();
|
||||
if (clone["persona"] is null || string.IsNullOrWhiteSpace(clone["persona"]?.ToString()))
|
||||
{
|
||||
if (t is JObject jo)
|
||||
{
|
||||
docs.Add(jo);
|
||||
}
|
||||
clone["persona"] = stamp;
|
||||
}
|
||||
}
|
||||
else if (parsed is JObject single)
|
||||
{
|
||||
docs.Add(single);
|
||||
docs.Add(clone);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -678,9 +703,15 @@ public sealed class AssistentConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
Scan(Path.Combine(_bundledRoot, "_base"));
|
||||
Scan(Path.Combine(_overlayRoot, "_base"));
|
||||
Scan(Path.Combine(_overlayRoot, "memory-seed"));
|
||||
|
||||
Scan(Path.Combine(_bundledRoot, "_base", "memory-seed"), AssistentMemory.SharedPersona);
|
||||
Scan(Path.Combine(_overlayRoot, "_base", "memory-seed"), AssistentMemory.SharedPersona);
|
||||
Scan(Path.Combine(_overlayRoot, "memory-seed"), AssistentMemory.SharedPersona);
|
||||
foreach (string id in PersonaIdsUnder(_bundledRoot).Concat(PersonaIdsUnder(_overlayRoot)).Distinct(StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
Scan(Path.Combine(_bundledRoot, "personas", id, "memory-seed"), id);
|
||||
Scan(Path.Combine(_overlayRoot, "personas", id, "memory-seed"), id);
|
||||
}
|
||||
return docs;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user