ei-tui 0.1.24 → 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 (98) 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 +34 -14
  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 +60 -46
  24. package/src/core/orchestrators/dedup-phase.ts +11 -5
  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 +113 -22
  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/dedup.ts +41 -7
  45. package/src/prompts/ceremony/rewrite.ts +3 -22
  46. package/src/prompts/ceremony/types.ts +3 -3
  47. package/src/prompts/generation/descriptions.ts +2 -2
  48. package/src/prompts/generation/types.ts +2 -2
  49. package/src/prompts/heartbeat/types.ts +2 -2
  50. package/src/prompts/human/event-scan.ts +122 -0
  51. package/src/prompts/human/fact-find.ts +106 -0
  52. package/src/prompts/human/fact-scan.ts +0 -2
  53. package/src/prompts/human/index.ts +17 -10
  54. package/src/prompts/human/person-match.ts +65 -0
  55. package/src/prompts/human/person-scan.ts +52 -59
  56. package/src/prompts/human/person-update.ts +241 -0
  57. package/src/prompts/human/topic-match.ts +65 -0
  58. package/src/prompts/human/topic-scan.ts +51 -71
  59. package/src/prompts/human/topic-update.ts +295 -0
  60. package/src/prompts/human/types.ts +63 -40
  61. package/src/prompts/index.ts +4 -8
  62. package/src/prompts/persona/topics-update.ts +2 -2
  63. package/src/prompts/persona/traits.ts +2 -2
  64. package/src/prompts/persona/types.ts +3 -3
  65. package/src/prompts/response/index.ts +1 -1
  66. package/src/prompts/response/sections.ts +9 -12
  67. package/src/prompts/response/types.ts +2 -3
  68. package/src/storage/embeddings.ts +1 -1
  69. package/src/storage/index.ts +1 -0
  70. package/src/storage/indexed.ts +174 -0
  71. package/src/storage/merge.ts +67 -2
  72. package/tui/src/app.tsx +7 -5
  73. package/tui/src/commands/archive.tsx +2 -2
  74. package/tui/src/commands/context.tsx +3 -4
  75. package/tui/src/commands/delete.tsx +4 -4
  76. package/tui/src/commands/dlq.ts +3 -4
  77. package/tui/src/commands/help.tsx +1 -1
  78. package/tui/src/commands/me.tsx +8 -18
  79. package/tui/src/commands/persona.tsx +2 -2
  80. package/tui/src/commands/provider.tsx +3 -5
  81. package/tui/src/commands/queue.ts +3 -4
  82. package/tui/src/commands/quotes.tsx +6 -8
  83. package/tui/src/commands/registry.ts +1 -1
  84. package/tui/src/commands/setsync.tsx +2 -2
  85. package/tui/src/commands/settings.tsx +18 -4
  86. package/tui/src/commands/spotify-auth.ts +0 -1
  87. package/tui/src/commands/tools.tsx +4 -5
  88. package/tui/src/context/ei.tsx +5 -14
  89. package/tui/src/context/overlay.tsx +17 -6
  90. package/tui/src/util/editor.ts +22 -11
  91. package/tui/src/util/persona-editor.tsx +6 -8
  92. package/tui/src/util/provider-editor.tsx +6 -8
  93. package/tui/src/util/toolkit-editor.tsx +3 -4
  94. package/tui/src/util/yaml-serializers.ts +48 -33
  95. package/src/cli/commands/traits.ts +0 -25
  96. package/src/prompts/human/item-match.ts +0 -74
  97. package/src/prompts/human/item-update.ts +0 -364
  98. package/src/prompts/human/trait-scan.ts +0 -115
@@ -1,57 +1,91 @@
1
- import type { LLMResponse } from "../types.js";
1
+ import type { LLMResponse, Fact } from "../types.js";
2
2
  import type { StateManager } from "../state-manager.js";
3
3
  import type {
4
- FactScanResult,
5
- TraitScanResult,
4
+ FactFindResult,
6
5
  TopicScanResult,
7
6
  PersonScanResult,
7
+ TopicScanCandidate,
8
8
  } from "../../prompts/human/types.js";
9
- import { queueItemMatch, type ExtractionContext } from "../orchestrators/index.js";
9
+ import { queueTopicMatch, queuePersonMatch, type ExtractionContext } from "../orchestrators/index.js";
10
10
  import { markMessagesExtracted } from "./utils.js";
11
+ import { BUILT_IN_FACT_NAMES } from "../constants/built-in-facts.js";
12
+ import { getEmbeddingService, getItemEmbeddingText } from "../embedding-service.js";
11
13
 
12
- export async function handleHumanFactScan(response: LLMResponse, state: StateManager): Promise<void> {
13
- const result = response.parsed as FactScanResult | undefined;
14
+ export async function handleFactFind(response: LLMResponse, state: StateManager): Promise<void> {
15
+ const result = response.parsed as FactFindResult | undefined;
14
16
 
15
17
  // Mark messages as scanned regardless of whether facts were found
16
18
  markMessagesExtracted(response, state, "f");
17
19
 
18
20
  if (!result?.facts || !Array.isArray(result.facts)) {
19
- console.log("[handleHumanFactScan] No facts detected or invalid result");
21
+ console.log("[handleFactFind] No facts detected or invalid result");
20
22
  return;
21
23
  }
22
24
 
23
25
  const context = response.request.data as unknown as ExtractionContext;
24
26
  if (!context?.personaId) return;
25
27
 
26
- for (const candidate of result.facts) {
27
- await queueItemMatch("fact", candidate, context, state);
28
- }
29
- console.log(`[handleHumanFactScan] Queued ${result.facts.length} fact(s) for matching`);
30
- }
28
+ const human = state.getHuman();
29
+ const now = new Date().toISOString();
30
+ let upsertCount = 0;
31
31
 
32
- export async function handleHumanTraitScan(response: LLMResponse, state: StateManager): Promise<void> {
33
- const result = response.parsed as TraitScanResult | undefined;
34
-
35
- markMessagesExtracted(response, state, "r");
36
-
37
- if (!result?.traits || !Array.isArray(result.traits)) {
38
- console.log("[handleHumanTraitScan] No traits detected or invalid result");
39
- return;
40
- }
32
+ for (const factResult of result.facts) {
33
+ // Only upsert facts that match a built-in name
34
+ if (!BUILT_IN_FACT_NAMES.has(factResult.name)) {
35
+ console.log(`[handleFactFind] Skipping non-built-in fact: "${factResult.name}"`);
36
+ continue;
37
+ }
41
38
 
42
- const context = response.request.data as unknown as ExtractionContext;
43
- if (!context?.personaId) return;
39
+ // Find the existing fact in state
40
+ const existingFact = human.facts.find(f => f.name === factResult.name);
41
+ if (!existingFact) {
42
+ console.log(`[handleFactFind] Skipping unknown fact: "${factResult.name}"`);
43
+ continue;
44
+ }
44
45
 
45
- for (const candidate of result.traits) {
46
- await queueItemMatch("trait", candidate, context, state);
46
+ // Skip facts that already have descriptions (only fill empty ones)
47
+ if (existingFact.description && existingFact.description !== "") {
48
+ console.log(`[handleFactFind] Skipping fact with existing description: "${factResult.name}"`);
49
+ continue;
50
+ }
51
+
52
+ // Skip if the LLM returned a null/empty value — don't store null descriptions
53
+ if (!factResult.value) {
54
+ console.log(`[handleFactFind] Skipping fact with null/empty value: "${factResult.name}"`);
55
+ continue;
56
+ }
57
+
58
+ // Compute embedding for the updated fact
59
+ let embedding: number[] | undefined;
60
+ try {
61
+ const embeddingService = getEmbeddingService();
62
+ const text = getItemEmbeddingText({ name: factResult.name, description: factResult.value });
63
+ embedding = await embeddingService.embed(text);
64
+ } catch (err) {
65
+ console.warn(`[handleFactFind] Failed to compute embedding for fact "${factResult.name}":`, err);
66
+ }
67
+
68
+ const updatedFact: Fact = {
69
+ ...existingFact,
70
+ description: factResult.value,
71
+ last_updated: now,
72
+ learned_by: existingFact.learned_by ?? context.personaId,
73
+ last_changed_by: context.personaId,
74
+ embedding,
75
+ };
76
+
77
+ state.human_fact_upsert(updatedFact);
78
+ upsertCount++;
47
79
  }
48
- console.log(`[handleHumanTraitScan] Queued ${result.traits.length} trait(s) for matching`);
80
+
81
+ console.log(`[handleFactFind] Upserted ${upsertCount} fact(s)`);
49
82
  }
50
83
 
84
+
51
85
  export async function handleHumanTopicScan(response: LLMResponse, state: StateManager): Promise<void> {
52
86
  const result = response.parsed as TopicScanResult | undefined;
53
87
 
54
- markMessagesExtracted(response, state, "p");
88
+ markMessagesExtracted(response, state, "t");
55
89
 
56
90
  if (!result?.topics || !Array.isArray(result.topics)) {
57
91
  console.log("[handleHumanTopicScan] No topics detected or invalid result");
@@ -61,8 +95,9 @@ export async function handleHumanTopicScan(response: LLMResponse, state: StateMa
61
95
  const context = response.request.data as unknown as ExtractionContext;
62
96
  if (!context?.personaId) return;
63
97
 
98
+ const extractionModel = (response.request.data as Record<string, unknown>).extraction_model as string | undefined;
64
99
  for (const candidate of result.topics) {
65
- await queueItemMatch("topic", candidate, context, state);
100
+ await queueTopicMatch(candidate, context, state, extractionModel);
66
101
  }
67
102
  console.log(`[handleHumanTopicScan] Queued ${result.topics.length} topic(s) for matching`);
68
103
  }
@@ -70,7 +105,7 @@ export async function handleHumanTopicScan(response: LLMResponse, state: StateMa
70
105
  export async function handleHumanPersonScan(response: LLMResponse, state: StateManager): Promise<void> {
71
106
  const result = response.parsed as PersonScanResult | undefined;
72
107
 
73
- markMessagesExtracted(response, state, "o");
108
+ markMessagesExtracted(response, state, "p");
74
109
 
75
110
  if (!result?.people || !Array.isArray(result.people)) {
76
111
  console.log("[handleHumanPersonScan] No people detected or invalid result");
@@ -80,8 +115,38 @@ export async function handleHumanPersonScan(response: LLMResponse, state: StateM
80
115
  const context = response.request.data as unknown as ExtractionContext;
81
116
  if (!context?.personaId) return;
82
117
 
118
+ const extractionModel = (response.request.data as Record<string, unknown>).extraction_model as string | undefined;
83
119
  for (const candidate of result.people) {
84
- await queueItemMatch("person", candidate, context, state);
120
+ await queuePersonMatch(candidate, context, state, extractionModel);
85
121
  }
86
122
  console.log(`[handleHumanPersonScan] Queued ${result.people.length} person(s) for matching`);
87
123
  }
124
+
125
+ export async function handleEventScan(response: LLMResponse, state: StateManager): Promise<void> {
126
+ markMessagesExtracted(response, state, "e");
127
+
128
+ const result = response.parsed as { events?: Array<{ name: string; description: string; reason: string }> } | undefined;
129
+
130
+ if (!result?.events || !Array.isArray(result.events) || result.events.length === 0) {
131
+ console.log("[handleEventScan] No epic events detected");
132
+ return;
133
+ }
134
+
135
+ const context = response.request.data as unknown as ExtractionContext;
136
+ if (!context?.personaId) return;
137
+
138
+ const extractionModel = (response.request.data as Record<string, unknown>).extraction_model as string | undefined;
139
+
140
+ for (const event of result.events) {
141
+ const candidate: TopicScanCandidate = {
142
+ name: event.name,
143
+ description: event.description,
144
+ category: "Event",
145
+ reason: event.reason,
146
+ };
147
+ await queueTopicMatch(candidate, context, state, extractionModel);
148
+ }
149
+
150
+ console.log(`[handleEventScan] Queued ${result.events.length} event(s) for matching`);
151
+ }
152
+