ei-tui 0.1.8 → 0.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ei-tui",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "author": "Flare576",
5
5
  "repository": {
6
6
  "type": "git",
@@ -233,6 +233,7 @@ function handleEiHeartbeat(response: LLMResponse, state: StateManager): void {
233
233
  timestamp: now,
234
234
  read: false,
235
235
  context_status: ContextStatus.Default,
236
+ f: true, r: true, p: true, o: true,
236
237
  });
237
238
 
238
239
  if (found.type === "fact") {
@@ -672,7 +673,7 @@ async function handleHumanItemUpdate(response: LLMResponse, state: StateManager)
672
673
  const isEi = personaDisplayName.toLowerCase() === "ei";
673
674
 
674
675
  const human = state.getHuman();
675
- const getExistingItem = (): { learned_by?: string; persona_groups?: string[] } | undefined => {
676
+ const getExistingItem = (): { learned_by?: string; last_changed_by?: string; persona_groups?: string[] } | undefined => {
676
677
  if (isNewItem) return undefined;
677
678
  switch (candidateType) {
678
679
  case "fact": return human.facts.find(f => f.id === existingItemId);
@@ -710,7 +711,8 @@ async function handleHumanItemUpdate(response: LLMResponse, state: StateManager)
710
711
  validated: ValidationLevel.None,
711
712
  validated_date: now,
712
713
  last_updated: now,
713
- learned_by: isNewItem ? personaDisplayName : existingItem?.learned_by,
714
+ learned_by: isNewItem ? personaId : existingItem?.learned_by,
715
+ last_changed_by: personaId,
714
716
  persona_groups: mergeGroups(existingItem?.persona_groups),
715
717
  embedding,
716
718
  };
@@ -725,7 +727,8 @@ async function handleHumanItemUpdate(response: LLMResponse, state: StateManager)
725
727
  sentiment: result.sentiment,
726
728
  strength: (result as any).strength ?? 0.5,
727
729
  last_updated: now,
728
- learned_by: isNewItem ? personaDisplayName : existingItem?.learned_by,
730
+ learned_by: isNewItem ? personaId : existingItem?.learned_by,
731
+ last_changed_by: personaId,
729
732
  persona_groups: mergeGroups(existingItem?.persona_groups),
730
733
  embedding,
731
734
  };
@@ -745,7 +748,8 @@ async function handleHumanItemUpdate(response: LLMResponse, state: StateManager)
745
748
  exposure_current: calculateExposureCurrent(exposureImpact),
746
749
  exposure_desired: (result as any).exposure_desired ?? 0.5,
747
750
  last_updated: now,
748
- learned_by: isNewItem ? personaDisplayName : existingItem?.learned_by,
751
+ learned_by: isNewItem ? personaId : existingItem?.learned_by,
752
+ last_changed_by: personaId,
749
753
  persona_groups: mergeGroups(existingItem?.persona_groups),
750
754
  embedding,
751
755
  };
@@ -763,7 +767,8 @@ async function handleHumanItemUpdate(response: LLMResponse, state: StateManager)
763
767
  exposure_current: calculateExposureCurrent(exposureImpact),
764
768
  exposure_desired: (result as any).exposure_desired ?? 0.5,
765
769
  last_updated: now,
766
- learned_by: isNewItem ? personaDisplayName : existingItem?.learned_by,
770
+ learned_by: isNewItem ? personaId : existingItem?.learned_by,
771
+ last_changed_by: personaId,
767
772
  persona_groups: mergeGroups(existingItem?.persona_groups),
768
773
  embedding,
769
774
  };
@@ -565,7 +565,7 @@ export class Processor {
565
565
  const items: EiHeartbeatItem[] = [];
566
566
 
567
567
  const unverifiedFacts = human.facts
568
- .filter(f => f.validated === ValidationLevel.None && f.learned_by !== "Ei")
568
+ .filter(f => f.validated === ValidationLevel.None && f.learned_by !== "ei" && (f.last_changed_by === undefined || f.last_changed_by !== "ei"))
569
569
  .slice(0, 5);
570
570
  for (const fact of unverifiedFacts) {
571
571
  const quote = human.quotes.find(q => q.data_item_ids.includes(fact.id));
@@ -36,11 +36,60 @@ export class StateManager {
36
36
  this.humanState.load(state.human);
37
37
  this.personaState.load(state.personas);
38
38
  this.queueState.load(state.queue);
39
+ this.migrateLearnedByToIds();
39
40
  } else {
40
41
  this.humanState.load(createDefaultHumanEntity());
41
42
  }
42
43
  }
43
44
 
45
+ /**
46
+ * Migration: learned_by used to store display names; now stores persona IDs.
47
+ * On load, attempt to resolve display names -> IDs using current persona map.
48
+ * Unresolvable values (renamed/deleted personas) are cleared to avoid stale display.
49
+ * No-op for already-migrated data (UUIDs or "ei" won't match display names).
50
+ */
51
+ private migrateLearnedByToIds(): void {
52
+ const personas = this.personaState.getAll();
53
+ const nameToId = new Map<string, string>();
54
+ for (const p of personas) {
55
+ nameToId.set(p.display_name.toLowerCase(), p.id);
56
+ for (const alias of p.aliases ?? []) {
57
+ nameToId.set(alias.toLowerCase(), p.id);
58
+ }
59
+ }
60
+ // "Ei" display name -> "ei" id (hardcoded, always valid)
61
+ nameToId.set("ei", "ei");
62
+
63
+ const human = this.humanState.get();
64
+ let dirty = false;
65
+ const migrateItem = (item: { learned_by?: string; last_changed_by?: string }) => {
66
+ if (item.learned_by && !this.isPersonaId(item.learned_by)) {
67
+ const resolved = nameToId.get(item.learned_by.toLowerCase());
68
+ item.learned_by = resolved ?? undefined; // clear if unresolvable
69
+ dirty = true;
70
+ }
71
+ if (item.last_changed_by && !this.isPersonaId(item.last_changed_by)) {
72
+ const resolved = nameToId.get(item.last_changed_by.toLowerCase());
73
+ item.last_changed_by = resolved ?? undefined;
74
+ dirty = true;
75
+ }
76
+ };
77
+ [...human.facts, ...human.traits, ...human.topics, ...human.people].forEach(migrateItem);
78
+ if (dirty) {
79
+ this.humanState.set(human);
80
+ console.log("[StateManager] Migrated learned_by fields from display names to persona IDs");
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Returns true if value looks like a persona ID (UUID or the special "ei" id).
86
+ * Display names are free-form strings that won't match UUID format.
87
+ */
88
+ private isPersonaId(value: string): boolean {
89
+ if (value === "ei") return true;
90
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
91
+ }
92
+
44
93
  private buildStorageState(): StorageState {
45
94
  return {
46
95
  version: 1,
package/src/core/types.ts CHANGED
@@ -62,7 +62,8 @@ export interface DataItemBase {
62
62
  description: string;
63
63
  sentiment: number;
64
64
  last_updated: string;
65
- learned_by?: string; // Persona ID that learned this item
65
+ learned_by?: string; // Persona ID that originally learned this item (stable UUID)
66
+ last_changed_by?: string; // Persona ID that most recently updated this item (stable UUID)
66
67
  persona_groups?: string[];
67
68
  embedding?: number[];
68
69
  }
@@ -272,7 +272,8 @@ async function ensureSessionTopic(
272
272
  const existingTopic = human.topics.find((t) => t.id === session.id);
273
273
 
274
274
  const firstAgent = await reader.getFirstAgent(session.id);
275
- const learnedBy = firstAgent ?? "build";
275
+ const firstPersona = firstAgent ? stateManager.persona_getByName(firstAgent) : null;
276
+ const learnedBy = firstPersona?.id ?? firstAgent ?? "build";
276
277
 
277
278
  if (existingTopic) {
278
279
  if (existingTopic.name !== session.title) {
@@ -350,7 +350,10 @@ export function humanToYAML(human: HumanEntity): string {
350
350
 
351
351
  return YAML.stringify(data, {
352
352
  lineWidth: 0,
353
- }).replace(/^(\s+validated:\s+\S+)$/mg, '$1 # none | ei | human');
353
+ })
354
+ .replace(/^(\s+validated:\s+\S+)$/mg, '$1 # none | ei | human')
355
+ .replace(/^(\s+)(learned_by: .+)$/mg, '$1# [read-only] $2')
356
+ .replace(/^(\s+)(last_changed_by: .+)$/mg, '$1# [read-only] $2');
354
357
  }
355
358
 
356
359
  export interface HumanYAMLResult {
@@ -365,7 +368,12 @@ export interface HumanYAMLResult {
365
368
  }
366
369
 
367
370
  export function humanFromYAML(yamlContent: string): HumanYAMLResult {
368
- const data = YAML.parse(yamlContent) as EditableHumanData;
371
+ // Strip read-only comment lines before parsing so users can't accidentally corrupt them
372
+ const stripped = yamlContent
373
+ .split('\n')
374
+ .filter(line => !/^\s*#\s*\[read-only\]/.test(line))
375
+ .join('\n');
376
+ const data = YAML.parse(stripped) as EditableHumanData;
369
377
 
370
378
  const deletedFactIds: string[] = [];
371
379
  const deletedTraitIds: string[] = [];