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.
- 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 +10 -16
- 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 +50 -39
- package/src/core/orchestrators/dedup-phase.ts +0 -1
- 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 +99 -17
- 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/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/commands/me.tsx +5 -14
- package/tui/src/commands/settings.tsx +15 -0
- package/tui/src/context/ei.tsx +5 -14
- 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
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
OpenCodeSettings,
|
|
8
8
|
BackupConfig,
|
|
9
9
|
Fact,
|
|
10
|
-
|
|
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:
|
|
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:
|
|
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,
|
|
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, ...
|
|
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, ...
|
|
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, ...
|
|
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
|
-
|
|
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
|
-
}
|