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.
- package/README.md +42 -0
- package/package.json +1 -1
- package/src/README.md +4 -11
- package/src/cli/README.md +4 -5
- package/src/cli/retrieval.ts +3 -25
- package/src/cli.ts +3 -7
- package/src/core/AGENTS.md +1 -1
- package/src/core/constants/built-in-facts.ts +49 -0
- package/src/core/constants/index.ts +1 -0
- package/src/core/context-utils.ts +0 -1
- package/src/core/embedding-service.ts +8 -0
- package/src/core/handlers/dedup.ts +34 -14
- package/src/core/handlers/heartbeat.ts +2 -3
- package/src/core/handlers/human-extraction.ts +95 -30
- package/src/core/handlers/human-matching.ts +326 -248
- package/src/core/handlers/index.ts +8 -6
- package/src/core/handlers/persona-generation.ts +8 -8
- package/src/core/handlers/rewrite.ts +4 -29
- package/src/core/handlers/utils.ts +23 -1
- package/src/core/heartbeat-manager.ts +2 -4
- package/src/core/human-data-manager.ts +5 -27
- package/src/core/message-manager.ts +10 -10
- package/src/core/orchestrators/ceremony.ts +60 -46
- package/src/core/orchestrators/dedup-phase.ts +11 -5
- package/src/core/orchestrators/human-extraction.ts +351 -207
- package/src/core/orchestrators/index.ts +6 -4
- package/src/core/orchestrators/persona-generation.ts +3 -3
- package/src/core/processor.ts +113 -22
- package/src/core/prompt-context-builder.ts +4 -6
- package/src/core/state/human.ts +1 -26
- package/src/core/state/personas.ts +2 -2
- package/src/core/state-manager.ts +107 -14
- package/src/core/tools/builtin/read-memory.ts +7 -8
- package/src/core/types/data-items.ts +2 -4
- package/src/core/types/entities.ts +6 -4
- package/src/core/types/enums.ts +6 -9
- package/src/core/types/llm.ts +2 -2
- package/src/core/utils/crossFind.ts +2 -5
- package/src/core/utils/event-windows.ts +31 -0
- package/src/integrations/claude-code/importer.ts +8 -4
- package/src/integrations/claude-code/types.ts +2 -0
- package/src/integrations/opencode/importer.ts +7 -3
- package/src/prompts/AGENTS.md +73 -1
- package/src/prompts/ceremony/dedup.ts +41 -7
- package/src/prompts/ceremony/rewrite.ts +3 -22
- package/src/prompts/ceremony/types.ts +3 -3
- package/src/prompts/generation/descriptions.ts +2 -2
- package/src/prompts/generation/types.ts +2 -2
- package/src/prompts/heartbeat/types.ts +2 -2
- package/src/prompts/human/event-scan.ts +122 -0
- package/src/prompts/human/fact-find.ts +106 -0
- package/src/prompts/human/fact-scan.ts +0 -2
- package/src/prompts/human/index.ts +17 -10
- package/src/prompts/human/person-match.ts +65 -0
- package/src/prompts/human/person-scan.ts +52 -59
- package/src/prompts/human/person-update.ts +241 -0
- package/src/prompts/human/topic-match.ts +65 -0
- package/src/prompts/human/topic-scan.ts +51 -71
- package/src/prompts/human/topic-update.ts +295 -0
- package/src/prompts/human/types.ts +63 -40
- package/src/prompts/index.ts +4 -8
- package/src/prompts/persona/topics-update.ts +2 -2
- package/src/prompts/persona/traits.ts +2 -2
- package/src/prompts/persona/types.ts +3 -3
- package/src/prompts/response/index.ts +1 -1
- package/src/prompts/response/sections.ts +9 -12
- package/src/prompts/response/types.ts +2 -3
- package/src/storage/embeddings.ts +1 -1
- package/src/storage/index.ts +1 -0
- package/src/storage/indexed.ts +174 -0
- package/src/storage/merge.ts +67 -2
- package/tui/src/app.tsx +7 -5
- package/tui/src/commands/archive.tsx +2 -2
- package/tui/src/commands/context.tsx +3 -4
- package/tui/src/commands/delete.tsx +4 -4
- package/tui/src/commands/dlq.ts +3 -4
- package/tui/src/commands/help.tsx +1 -1
- package/tui/src/commands/me.tsx +8 -18
- package/tui/src/commands/persona.tsx +2 -2
- package/tui/src/commands/provider.tsx +3 -5
- package/tui/src/commands/queue.ts +3 -4
- package/tui/src/commands/quotes.tsx +6 -8
- package/tui/src/commands/registry.ts +1 -1
- package/tui/src/commands/setsync.tsx +2 -2
- package/tui/src/commands/settings.tsx +18 -4
- package/tui/src/commands/spotify-auth.ts +0 -1
- package/tui/src/commands/tools.tsx +4 -5
- package/tui/src/context/ei.tsx +5 -14
- package/tui/src/context/overlay.tsx +17 -6
- package/tui/src/util/editor.ts +22 -11
- package/tui/src/util/persona-editor.tsx +6 -8
- package/tui/src/util/provider-editor.tsx +6 -8
- package/tui/src/util/toolkit-editor.tsx +3 -4
- package/tui/src/util/yaml-serializers.ts +48 -33
- package/src/cli/commands/traits.ts +0 -25
- package/src/prompts/human/item-match.ts +0 -74
- package/src/prompts/human/item-update.ts +0 -364
- 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
|
-
|
|
5
|
-
TraitScanResult,
|
|
4
|
+
FactFindResult,
|
|
6
5
|
TopicScanResult,
|
|
7
6
|
PersonScanResult,
|
|
7
|
+
TopicScanCandidate,
|
|
8
8
|
} from "../../prompts/human/types.js";
|
|
9
|
-
import {
|
|
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
|
|
13
|
-
const result = response.parsed as
|
|
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("[
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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, "
|
|
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
|
|
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, "
|
|
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
|
|
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
|
+
|