ei-tui 0.1.25 → 0.2.0

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.
Files changed (78) hide show
  1. package/README.md +42 -0
  2. package/package.json +1 -1
  3. package/src/README.md +4 -11
  4. package/src/cli/README.md +4 -5
  5. package/src/cli/retrieval.ts +3 -25
  6. package/src/cli.ts +3 -7
  7. package/src/core/AGENTS.md +1 -1
  8. package/src/core/constants/built-in-facts.ts +49 -0
  9. package/src/core/constants/index.ts +1 -0
  10. package/src/core/context-utils.ts +0 -1
  11. package/src/core/embedding-service.ts +8 -0
  12. package/src/core/handlers/dedup.ts +10 -16
  13. package/src/core/handlers/heartbeat.ts +2 -3
  14. package/src/core/handlers/human-extraction.ts +95 -30
  15. package/src/core/handlers/human-matching.ts +326 -248
  16. package/src/core/handlers/index.ts +8 -6
  17. package/src/core/handlers/persona-generation.ts +8 -8
  18. package/src/core/handlers/rewrite.ts +4 -29
  19. package/src/core/handlers/utils.ts +23 -1
  20. package/src/core/heartbeat-manager.ts +2 -4
  21. package/src/core/human-data-manager.ts +5 -27
  22. package/src/core/message-manager.ts +10 -10
  23. package/src/core/orchestrators/ceremony.ts +50 -39
  24. package/src/core/orchestrators/dedup-phase.ts +0 -1
  25. package/src/core/orchestrators/human-extraction.ts +351 -207
  26. package/src/core/orchestrators/index.ts +6 -4
  27. package/src/core/orchestrators/persona-generation.ts +3 -3
  28. package/src/core/processor.ts +99 -17
  29. package/src/core/prompt-context-builder.ts +4 -6
  30. package/src/core/state/human.ts +1 -26
  31. package/src/core/state/personas.ts +2 -2
  32. package/src/core/state-manager.ts +107 -14
  33. package/src/core/tools/builtin/read-memory.ts +7 -8
  34. package/src/core/types/data-items.ts +2 -4
  35. package/src/core/types/entities.ts +6 -4
  36. package/src/core/types/enums.ts +6 -9
  37. package/src/core/types/llm.ts +2 -2
  38. package/src/core/utils/crossFind.ts +2 -5
  39. package/src/core/utils/event-windows.ts +31 -0
  40. package/src/integrations/claude-code/importer.ts +8 -4
  41. package/src/integrations/claude-code/types.ts +2 -0
  42. package/src/integrations/opencode/importer.ts +7 -3
  43. package/src/prompts/AGENTS.md +73 -1
  44. package/src/prompts/ceremony/rewrite.ts +3 -22
  45. package/src/prompts/ceremony/types.ts +3 -3
  46. package/src/prompts/generation/descriptions.ts +2 -2
  47. package/src/prompts/generation/types.ts +2 -2
  48. package/src/prompts/heartbeat/types.ts +2 -2
  49. package/src/prompts/human/event-scan.ts +122 -0
  50. package/src/prompts/human/fact-find.ts +106 -0
  51. package/src/prompts/human/fact-scan.ts +0 -2
  52. package/src/prompts/human/index.ts +17 -10
  53. package/src/prompts/human/person-match.ts +65 -0
  54. package/src/prompts/human/person-scan.ts +52 -59
  55. package/src/prompts/human/person-update.ts +241 -0
  56. package/src/prompts/human/topic-match.ts +65 -0
  57. package/src/prompts/human/topic-scan.ts +51 -71
  58. package/src/prompts/human/topic-update.ts +295 -0
  59. package/src/prompts/human/types.ts +63 -40
  60. package/src/prompts/index.ts +4 -8
  61. package/src/prompts/persona/topics-update.ts +2 -2
  62. package/src/prompts/persona/traits.ts +2 -2
  63. package/src/prompts/persona/types.ts +3 -3
  64. package/src/prompts/response/index.ts +1 -1
  65. package/src/prompts/response/sections.ts +9 -12
  66. package/src/prompts/response/types.ts +2 -3
  67. package/src/storage/embeddings.ts +1 -1
  68. package/src/storage/index.ts +1 -0
  69. package/src/storage/indexed.ts +174 -0
  70. package/src/storage/merge.ts +67 -2
  71. package/tui/src/commands/me.tsx +5 -14
  72. package/tui/src/commands/settings.tsx +15 -0
  73. package/tui/src/context/ei.tsx +5 -14
  74. package/tui/src/util/yaml-serializers.ts +48 -33
  75. package/src/cli/commands/traits.ts +0 -25
  76. package/src/prompts/human/item-match.ts +0 -74
  77. package/src/prompts/human/item-update.ts +0 -364
  78. package/src/prompts/human/trait-scan.ts +0 -115
@@ -7,7 +7,7 @@ import type {
7
7
  OpenCodeSettings,
8
8
  BackupConfig,
9
9
  Fact,
10
- Trait,
10
+ PersonaTrait,
11
11
  Topic,
12
12
  Person,
13
13
  PersonaTopic,
@@ -23,15 +23,12 @@ import type {
23
23
  } from "../../../src/core/types.js";
24
24
  import { ContextStatus } from "../../../src/core/types.js";
25
25
  import type { ClaudeCodeSettings } from "../../../src/integrations/claude-code/types.js";
26
+ import { BUILT_IN_FACT_NAMES } from "../../../src/core/constants/built-in-facts.js";
26
27
 
27
28
  // =============================================================================
28
29
  // TYPES FOR YAML EDITING
29
30
  // =============================================================================
30
31
 
31
- interface EditableTrait extends Trait {
32
- _delete?: boolean;
33
- }
34
-
35
32
  interface EditableTopic extends Topic {
36
33
  _delete?: boolean;
37
34
  }
@@ -64,7 +61,6 @@ interface EditablePersonaData {
64
61
 
65
62
  interface EditableHumanData {
66
63
  facts: EditableFact[];
67
- traits: EditableTrait[];
68
64
  topics: EditableTopic[];
69
65
  people: EditablePerson[];
70
66
  }
@@ -190,7 +186,7 @@ export function newPersonaFromYAML(yamlContent: string, allTools?: ToolDefinitio
190
186
  t.name === PLACEHOLDER_TRAIT.name &&
191
187
  t.description === PLACEHOLDER_TRAIT.description;
192
188
 
193
- const traits: Trait[] = [];
189
+ const traits: PersonaTrait[] = [];
194
190
  for (const t of data.traits ?? []) {
195
191
  if (isTraitPlaceholder(t)) {
196
192
  continue;
@@ -313,7 +309,7 @@ export function personaFromYAML(yamlContent: string, original: PersonaEntity, al
313
309
  t.name === PLACEHOLDER_TRAIT.name &&
314
310
  t.description === PLACEHOLDER_TRAIT.description;
315
311
 
316
- const traits: Trait[] = [];
312
+ const traits: PersonaTrait[] = [];
317
313
  for (const t of data.traits ?? []) {
318
314
  if (isTraitPlaceholder(t)) {
319
315
  continue;
@@ -406,7 +402,6 @@ export function personaFromYAML(yamlContent: string, original: PersonaEntity, al
406
402
  export function humanToYAML(human: HumanEntity, personaLookup?: Map<string, string>): string {
407
403
  const data: EditableHumanData = {
408
404
  facts: human.facts.map(f => ({ ...f, _delete: false })),
409
- traits: human.traits.map(t => ({ ...t, _delete: false })),
410
405
  topics: human.topics.map(t => ({ ...t, _delete: false })),
411
406
  people: human.people.map(p => ({ ...p, _delete: false })),
412
407
  };
@@ -414,27 +409,28 @@ export function humanToYAML(human: HumanEntity, personaLookup?: Map<string, stri
414
409
  return YAML.stringify(data, {
415
410
  lineWidth: 0,
416
411
  })
417
- .replace(/^(\s+validated:\s+\S+)$/mg, '$1 # none | ei | human')
418
412
  .replace(/^(\s+)(learned_by: )(.+)$/mg, (_, indent, key, val) => {
419
413
  const trimmed = val.trim();
420
414
  const displayName = personaLookup?.get(trimmed) ?? trimmed;
421
415
  return `${indent}# [read-only] ${key}${displayName}`;
422
416
  })
423
- .replace(/^(\s+)(last_changed_by: .+)$/mg, '$1# [read-only] $2');
417
+ .replace(/^(\s+)(last_changed_by: )(.+)$/mg, (_, indent, key, val) => {
418
+ const trimmed = val.trim();
419
+ const displayName = personaLookup?.get(trimmed) ?? trimmed;
420
+ return `${indent}# [read-only] ${key}${displayName}`;
421
+ });
424
422
  }
425
423
 
426
424
  export interface HumanYAMLResult {
427
425
  facts: Fact[];
428
- traits: Trait[];
429
426
  topics: Topic[];
430
427
  people: Person[];
431
428
  deletedFactIds: string[];
432
- deletedTraitIds: string[];
433
429
  deletedTopicIds: string[];
434
430
  deletedPersonIds: string[];
435
431
  }
436
432
 
437
- export function humanFromYAML(yamlContent: string): HumanYAMLResult {
433
+ export function humanFromYAML(yamlContent: string, original?: HumanEntity): HumanYAMLResult {
438
434
  // Strip read-only comment lines before parsing so users can't accidentally corrupt them
439
435
  const stripped = yamlContent
440
436
  .split('\n')
@@ -443,36 +439,32 @@ export function humanFromYAML(yamlContent: string): HumanYAMLResult {
443
439
  const data = YAML.parse(stripped) as EditableHumanData;
444
440
 
445
441
  const deletedFactIds: string[] = [];
446
- const deletedTraitIds: string[] = [];
447
442
  const deletedTopicIds: string[] = [];
448
443
  const deletedPersonIds: string[] = [];
449
444
 
450
445
  const facts: Fact[] = [];
451
446
  for (const f of data.facts ?? []) {
452
- if (f._delete) {
447
+ if (f._delete && !BUILT_IN_FACT_NAMES.has(f.name)) {
453
448
  deletedFactIds.push(f.id);
454
449
  } else {
455
- const { _delete, ...fact } = f;
450
+ const { _delete, ...parsed } = f;
451
+ const originalFact = original?.facts.find(of => of.id === parsed.id);
452
+ const fact = originalFact ? { ...originalFact, ...parsed } : parsed;
453
+ if (fact.description && !fact.validated_date) {
454
+ fact.validated_date = new Date().toISOString();
455
+ }
456
456
  facts.push(fact);
457
457
  }
458
458
  }
459
459
 
460
- const traits: Trait[] = [];
461
- for (const t of data.traits ?? []) {
462
- if (t._delete) {
463
- deletedTraitIds.push(t.id);
464
- } else {
465
- const { _delete, ...trait } = t;
466
- traits.push(trait);
467
- }
468
- }
469
-
470
460
  const topics: Topic[] = [];
471
461
  for (const t of data.topics ?? []) {
472
462
  if (t._delete) {
473
463
  deletedTopicIds.push(t.id);
474
464
  } else {
475
- const { _delete, ...topic } = t;
465
+ const { _delete, ...parsed } = t;
466
+ const originalTopic = original?.topics.find(ot => ot.id === parsed.id);
467
+ const topic = originalTopic ? { ...originalTopic, ...parsed } : parsed;
476
468
  topics.push(topic);
477
469
  }
478
470
  }
@@ -482,18 +474,18 @@ export function humanFromYAML(yamlContent: string): HumanYAMLResult {
482
474
  if (p._delete) {
483
475
  deletedPersonIds.push(p.id);
484
476
  } else {
485
- const { _delete, ...person } = p;
477
+ const { _delete, ...parsed } = p;
478
+ const originalPerson = original?.people.find(op => op.id === parsed.id);
479
+ const person = originalPerson ? { ...originalPerson, ...parsed } : parsed;
486
480
  people.push(person);
487
481
  }
488
482
  }
489
483
 
490
484
  return {
491
485
  facts,
492
- traits,
493
486
  topics,
494
487
  people,
495
488
  deletedFactIds,
496
- deletedTraitIds,
497
489
  deletedTopicIds,
498
490
  deletedPersonIds,
499
491
  };
@@ -520,11 +512,15 @@ interface EditableSettingsData {
520
512
  polling_interval_ms?: number | null;
521
513
  last_sync?: string | null;
522
514
  extraction_point?: string | null;
515
+ extraction_model?: string | null;
516
+ extraction_token_limit?: string | number | null;
523
517
  };
524
518
  claudeCode?: {
525
519
  integration?: boolean | null;
526
520
  polling_interval_ms?: number | null;
527
521
  last_sync?: string | null;
522
+ extraction_model?: string | null;
523
+ extraction_token_limit?: string | number | null;
528
524
  };
529
525
  backup?: {
530
526
  enabled?: boolean | null;
@@ -550,12 +546,16 @@ export function settingsToYAML(settings: HumanSettings | undefined): string {
550
546
  opencode: {
551
547
  integration: settings?.opencode?.integration ?? false,
552
548
  polling_interval_ms: settings?.opencode?.polling_interval_ms ?? 1800000,
549
+ extraction_model: settings?.opencode?.extraction_model ?? 'default',
550
+ extraction_token_limit: settings?.opencode?.extraction_token_limit ?? 'default',
553
551
  last_sync: settings?.opencode?.last_sync ?? null,
554
552
  extraction_point: settings?.opencode?.extraction_point ?? null,
555
553
  },
556
554
  claudeCode: {
557
555
  integration: settings?.claudeCode?.integration ?? false,
558
556
  polling_interval_ms: settings?.claudeCode?.polling_interval_ms ?? 1800000,
557
+ extraction_model: settings?.claudeCode?.extraction_model ?? 'default',
558
+ extraction_token_limit: settings?.claudeCode?.extraction_token_limit ?? 'default',
559
559
  last_sync: settings?.claudeCode?.last_sync ?? null,
560
560
  },
561
561
  backup: {
@@ -569,7 +569,9 @@ export function settingsToYAML(settings: HumanSettings | undefined): string {
569
569
  lineWidth: 0,
570
570
  })
571
571
  .replace(/^(\s+)(last_sync: .+)$/mg, '$1# [read-only] $2')
572
- .replace(/^(\s+)(extraction_point: .+)$/mg, '$1# [read-only] $2');
572
+ .replace(/^(\s+)(extraction_point: .+)$/mg, '$1# [read-only] $2')
573
+ .replace(/^(\s+)(extraction_model: .+)$/mg, '$1$2 # e.g. Anthropic:claude-haiku-4-5')
574
+ .replace(/^(\s+)(extraction_token_limit: .+)$/mg, '$1$2 # e.g. 100000 for Haiku');
573
575
  }
574
576
  export function settingsFromYAML(yamlContent: string, original: HumanSettings | undefined): HumanSettings {
575
577
  const data = YAML.parse(yamlContent) as EditableSettingsData;
@@ -596,6 +598,12 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
596
598
  last_sync: original?.opencode?.last_sync,
597
599
  extraction_point: original?.opencode?.extraction_point,
598
600
  processed_sessions: original?.opencode?.processed_sessions,
601
+ extraction_model: (data.opencode.extraction_model != null && data.opencode.extraction_model !== 'default')
602
+ ? data.opencode.extraction_model
603
+ : undefined,
604
+ extraction_token_limit: (data.opencode.extraction_token_limit != null && data.opencode.extraction_token_limit !== 'default')
605
+ ? Number(data.opencode.extraction_token_limit)
606
+ : undefined,
599
607
  };
600
608
  }
601
609
 
@@ -606,6 +614,12 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
606
614
  polling_interval_ms: nullToUndefined(data.claudeCode.polling_interval_ms),
607
615
  last_sync: original?.claudeCode?.last_sync,
608
616
  processed_sessions: original?.claudeCode?.processed_sessions,
617
+ extraction_model: (data.claudeCode.extraction_model != null && data.claudeCode.extraction_model !== 'default')
618
+ ? data.claudeCode.extraction_model
619
+ : undefined,
620
+ extraction_token_limit: (data.claudeCode.extraction_token_limit != null && data.claudeCode.extraction_token_limit !== 'default')
621
+ ? Number(data.claudeCode.extraction_token_limit)
622
+ : undefined,
609
623
  };
610
624
  }
611
625
 
@@ -896,7 +910,8 @@ export function contextFromYAML(yamlContent: string): ContextYAMLResult {
896
910
  if (m._delete) {
897
911
  deletedMessageIds.push(m.id);
898
912
  } else {
899
- messages.push({ id: m.id, context_status: m.context_status });
913
+ const normalized = (m.context_status ?? 'default').toString().toLowerCase() as ContextStatus;
914
+ messages.push({ id: m.id, context_status: normalized });
900
915
  }
901
916
  }
902
917
 
@@ -1,25 +0,0 @@
1
- import { loadLatestState, retrieve } from "../retrieval";
2
- import type { TraitResult } from "../retrieval";
3
-
4
- export async function execute(query: string, limit: number): Promise<TraitResult[]> {
5
- const state = await loadLatestState();
6
- if (!state) {
7
- console.error("No saved state found. Is EI_DATA_PATH set correctly?");
8
- return [];
9
- }
10
-
11
- const traits = state.human.traits;
12
- if (traits.length === 0) {
13
- return [];
14
- }
15
-
16
- const results = await retrieve(traits, query, limit);
17
-
18
- return results.map(trait => ({
19
- id: trait.id,
20
- name: trait.name,
21
- description: trait.description,
22
- strength: trait.strength ?? 0.5,
23
- sentiment: trait.sentiment,
24
- }));
25
- }
@@ -1,74 +0,0 @@
1
- import type { ItemMatchPromptData, PromptOutput } from "./types.js";
2
-
3
- export function buildHumanItemMatchPrompt(data: ItemMatchPromptData): PromptOutput {
4
- if (!data.candidate_type || !data.candidate_name) {
5
- throw new Error("buildHumanItemMatchPrompt: candidate_type and candidate_name are required");
6
- }
7
-
8
- const typeLabel = data.candidate_type.toUpperCase();
9
-
10
- const system = `# Task
11
-
12
- You are checking if a new ${typeLabel} already exists in our database.
13
-
14
- We track four types of data about the human user:
15
- - **FACT**: Biographical data (name, birthday, job, etc.)
16
- - **TRAIT**: Personality patterns (introverted, analytical, etc.)
17
- - **TOPIC**: Interests, goals, concerns, stories
18
- - **PERSON**: People in their life (family, friends, coworkers)
19
-
20
- Your job is to find if this candidate matches ANY existing entry — even if it's stored as a different type.
21
-
22
- ## Why Cross-Type Matching?
23
-
24
- Sometimes the same concept gets detected as different types:
25
- - "Juliet" might be detected as a TOPIC but should be a PERSON
26
- - "Birthday" might be detected as a TOPIC but is actually a FACT
27
- - "Always planning ahead" might be a TOPIC but is really a TRAIT
28
-
29
- If you find a match in a DIFFERENT type, still return it! The system will handle the type mismatch.
30
-
31
- ## Matching Rules
32
-
33
- 1. **Exact match**: Same name/concept → return its ID
34
- 2. **Similar match**: Clearly the same thing with different wording → return its ID
35
- 3. **Cross-type match**: Same concept stored as different type → return its ID
36
- 4. **No match**: Genuinely new information → return "new"
37
-
38
- # Existing Data
39
-
40
- The following entries are already in our database. Same-type entries have full descriptions; cross-type entries are truncated for brevity.
41
-
42
- \`\`\`json
43
- ${JSON.stringify(data.all_items, null, 2)}
44
- \`\`\`
45
-
46
- # Response Format
47
-
48
- Return ONLY the ID of the matching entry, or "new" if no match exists.
49
-
50
- \`\`\`json
51
- {
52
- "matched_guid": "uuid-of-matching-entry" | "new"
53
- }
54
- \`\`\`
55
-
56
- **Return JSON only.**`;
57
-
58
- const user = `# Candidate to Match
59
-
60
- Type: ${typeLabel}
61
- Name: ${data.candidate_name}
62
- Value: ${data.candidate_value}
63
-
64
- Find the best match in existing data, or return "new" if this is genuinely new.
65
-
66
- **Return JSON:**
67
- \`\`\`json
68
- {
69
- "matched_guid": "..." | "new"
70
- }
71
- \`\`\``;
72
-
73
- return { system, user };
74
- }