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
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export { orchestratePersonaGeneration, type PartialPersona } from "./persona-generation.js";
|
|
2
2
|
export {
|
|
3
|
-
|
|
4
|
-
queueTraitScan,
|
|
3
|
+
queueFactFind,
|
|
5
4
|
queueTopicScan,
|
|
6
5
|
queuePersonScan,
|
|
7
6
|
queueAllScans,
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
queueTopicMatch,
|
|
8
|
+
queueTopicUpdate,
|
|
9
|
+
queuePersonMatch,
|
|
10
|
+
queuePersonUpdate,
|
|
11
|
+
queueEventSummary,
|
|
10
12
|
type ExtractionContext,
|
|
11
13
|
type ExtractionOptions,
|
|
12
14
|
} from "./human-extraction.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LLMRequestType, LLMPriority, LLMNextStep, type
|
|
1
|
+
import { LLMRequestType, LLMPriority, LLMNextStep, type PersonaTrait, type PersonaTopic } from "../types.js";
|
|
2
2
|
import type { StateManager } from "../state-manager.js";
|
|
3
3
|
import { buildPersonaGenerationPrompt } from "../../prompts/index.js";
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ export interface PartialPersona {
|
|
|
11
11
|
description?: string;
|
|
12
12
|
short_description?: string;
|
|
13
13
|
long_description?: string;
|
|
14
|
-
traits?: Partial<
|
|
14
|
+
traits?: Partial<PersonaTrait>[];
|
|
15
15
|
topics?: Partial<PersonaTopic>[];
|
|
16
16
|
model?: string;
|
|
17
17
|
group_primary?: string;
|
|
@@ -66,7 +66,7 @@ export function orchestratePersonaGeneration(
|
|
|
66
66
|
stateManager.persona_update(partial.id, {
|
|
67
67
|
short_description: partial.short_description,
|
|
68
68
|
long_description: partial.long_description,
|
|
69
|
-
traits: partial.traits as
|
|
69
|
+
traits: partial.traits as PersonaTrait[],
|
|
70
70
|
topics: partial.topics as PersonaTopic[],
|
|
71
71
|
last_updated: now,
|
|
72
72
|
});
|
package/src/core/processor.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
type MessageQueryOptions,
|
|
10
10
|
type HumanEntity,
|
|
11
11
|
type Fact,
|
|
12
|
-
type Trait,
|
|
13
12
|
type Topic,
|
|
14
13
|
type Person,
|
|
15
14
|
type Quote,
|
|
@@ -33,6 +32,7 @@ import { registerReadMemoryExecutor, registerFileReadExecutor } from "./tools/in
|
|
|
33
32
|
import { createReadMemoryExecutor } from "./tools/builtin/read-memory.js";
|
|
34
33
|
import { EI_WELCOME_MESSAGE, EI_PERSONA_DEFINITION } from "../templates/welcome.js";
|
|
35
34
|
import { shouldStartCeremony, startCeremony, handleCeremonyProgress } from "./orchestrators/index.js";
|
|
35
|
+
import { BUILT_IN_FACTS } from "./constants/built-in-facts.js";
|
|
36
36
|
|
|
37
37
|
// Static module imports
|
|
38
38
|
import {
|
|
@@ -70,7 +70,6 @@ import {
|
|
|
70
70
|
getHuman,
|
|
71
71
|
updateHuman,
|
|
72
72
|
upsertFact,
|
|
73
|
-
upsertTrait,
|
|
74
73
|
upsertTopic,
|
|
75
74
|
upsertPerson,
|
|
76
75
|
removeDataItem,
|
|
@@ -191,10 +190,16 @@ export class Processor {
|
|
|
191
190
|
}
|
|
192
191
|
}
|
|
193
192
|
|
|
193
|
+
await this.completeInitialization();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private async completeInitialization(): Promise<void> {
|
|
194
197
|
if (!this.stateManager.hasExistingData() || this.stateManager.persona_getAll().length === 0) {
|
|
195
198
|
await this.bootstrapFirstRun();
|
|
196
199
|
}
|
|
197
200
|
this.bootstrapTools();
|
|
201
|
+
this.seedBuiltinFacts();
|
|
202
|
+
this.seedSettings();
|
|
198
203
|
registerReadMemoryExecutor(createReadMemoryExecutor(this.searchHumanData.bind(this)));
|
|
199
204
|
if (this.isTUI) {
|
|
200
205
|
await registerFileReadExecutor();
|
|
@@ -571,6 +576,87 @@ export class Processor {
|
|
|
571
576
|
}
|
|
572
577
|
}
|
|
573
578
|
|
|
579
|
+
/**
|
|
580
|
+
* Seed 25 built-in facts if they don't exist yet.
|
|
581
|
+
* Called on every startup — safe to call repeatedly.
|
|
582
|
+
* New facts are created with empty descriptions and validated_date.
|
|
583
|
+
*/
|
|
584
|
+
private seedBuiltinFacts(): void {
|
|
585
|
+
const human = this.stateManager.getHuman();
|
|
586
|
+
const existingFactNames = new Set(human.facts.map(f => f.name));
|
|
587
|
+
|
|
588
|
+
// BUILT_IN_FACTS imported at top of file
|
|
589
|
+
const now = new Date().toISOString();
|
|
590
|
+
let seededCount = 0;
|
|
591
|
+
|
|
592
|
+
for (const builtInFact of BUILT_IN_FACTS) {
|
|
593
|
+
if (existingFactNames.has(builtInFact.name)) continue;
|
|
594
|
+
|
|
595
|
+
const newFact: Fact = {
|
|
596
|
+
id: crypto.randomUUID(),
|
|
597
|
+
name: builtInFact.name,
|
|
598
|
+
description: '',
|
|
599
|
+
sentiment: 0,
|
|
600
|
+
validated_date: '',
|
|
601
|
+
last_updated: now,
|
|
602
|
+
};
|
|
603
|
+
human.facts.push(newFact);
|
|
604
|
+
seededCount++;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (seededCount > 0) {
|
|
608
|
+
this.stateManager.setHuman(human);
|
|
609
|
+
console.log(`[Processor] Seeded ${seededCount} built-in facts`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private seedSettings(): void {
|
|
614
|
+
const human = this.stateManager.getHuman();
|
|
615
|
+
let modified = false;
|
|
616
|
+
|
|
617
|
+
if (!human.settings) {
|
|
618
|
+
human.settings = {};
|
|
619
|
+
modified = true;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (!human.settings.opencode) {
|
|
623
|
+
human.settings.opencode = {
|
|
624
|
+
integration: false,
|
|
625
|
+
polling_interval_ms: 1800000,
|
|
626
|
+
};
|
|
627
|
+
modified = true;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (!human.settings.claudeCode) {
|
|
631
|
+
human.settings.claudeCode = {
|
|
632
|
+
integration: false,
|
|
633
|
+
polling_interval_ms: 1800000,
|
|
634
|
+
};
|
|
635
|
+
modified = true;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (!human.settings.ceremony) {
|
|
639
|
+
human.settings.ceremony = {
|
|
640
|
+
time: "09:00",
|
|
641
|
+
};
|
|
642
|
+
modified = true;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (!human.settings.backup) {
|
|
646
|
+
human.settings.backup = {
|
|
647
|
+
enabled: false,
|
|
648
|
+
max_backups: 24,
|
|
649
|
+
interval_ms: 3600000,
|
|
650
|
+
};
|
|
651
|
+
modified = true;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (modified) {
|
|
655
|
+
this.stateManager.setHuman(human);
|
|
656
|
+
console.log(`[Processor] Seeded missing settings`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
574
660
|
async stop(): Promise<void> {
|
|
575
661
|
console.log(
|
|
576
662
|
`[Processor ${this.instanceId}] stop() called, running=${this.running}, stopped=${this.stopped}`
|
|
@@ -676,13 +762,7 @@ export class Processor {
|
|
|
676
762
|
|
|
677
763
|
this.pendingConflict = null;
|
|
678
764
|
this.importAbortController = new AbortController();
|
|
679
|
-
this.
|
|
680
|
-
registerReadMemoryExecutor(createReadMemoryExecutor(this.searchHumanData.bind(this)));
|
|
681
|
-
if (this.isTUI) {
|
|
682
|
-
await registerFileReadExecutor();
|
|
683
|
-
}
|
|
684
|
-
this.running = true;
|
|
685
|
-
this.runLoop();
|
|
765
|
+
await this.completeInitialization();
|
|
686
766
|
this.interface.onStateImported?.();
|
|
687
767
|
}
|
|
688
768
|
|
|
@@ -1093,7 +1173,10 @@ const toolNextSteps = new Set([
|
|
|
1093
1173
|
}
|
|
1094
1174
|
}
|
|
1095
1175
|
|
|
1096
|
-
if (
|
|
1176
|
+
if (
|
|
1177
|
+
response.request.next_step === LLMNextStep.HandleTopicUpdate ||
|
|
1178
|
+
response.request.next_step === LLMNextStep.HandlePersonUpdate
|
|
1179
|
+
) {
|
|
1097
1180
|
this.interface.onHumanUpdated?.();
|
|
1098
1181
|
this.interface.onQuoteAdded?.();
|
|
1099
1182
|
}
|
|
@@ -1102,6 +1185,10 @@ const toolNextSteps = new Set([
|
|
|
1102
1185
|
this.interface.onHumanUpdated?.();
|
|
1103
1186
|
}
|
|
1104
1187
|
|
|
1188
|
+
if (response.request.next_step === LLMNextStep.HandleFactFind) {
|
|
1189
|
+
this.interface.onHumanUpdated?.();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1105
1192
|
if (typeof response.request.data.ceremony_progress === "number") {
|
|
1106
1193
|
handleCeremonyProgress(this.stateManager, response.request.data.ceremony_progress);
|
|
1107
1194
|
}
|
|
@@ -1265,10 +1352,6 @@ const toolNextSteps = new Set([
|
|
|
1265
1352
|
this.interface.onHumanUpdated?.();
|
|
1266
1353
|
}
|
|
1267
1354
|
|
|
1268
|
-
async upsertTrait(trait: Trait): Promise<void> {
|
|
1269
|
-
await upsertTrait(this.stateManager, trait);
|
|
1270
|
-
this.interface.onHumanUpdated?.();
|
|
1271
|
-
}
|
|
1272
1355
|
|
|
1273
1356
|
async upsertTopic(topic: Topic): Promise<void> {
|
|
1274
1357
|
await upsertTopic(this.stateManager, topic);
|
|
@@ -1281,7 +1364,7 @@ const toolNextSteps = new Set([
|
|
|
1281
1364
|
}
|
|
1282
1365
|
|
|
1283
1366
|
async removeDataItem(
|
|
1284
|
-
type: "fact" | "
|
|
1367
|
+
type: "fact" | "topic" | "person",
|
|
1285
1368
|
id: string
|
|
1286
1369
|
): Promise<void> {
|
|
1287
1370
|
await removeDataItem(this.stateManager, type, id);
|
|
@@ -1313,10 +1396,9 @@ const toolNextSteps = new Set([
|
|
|
1313
1396
|
|
|
1314
1397
|
async searchHumanData(
|
|
1315
1398
|
query: string,
|
|
1316
|
-
options: { types?: Array<"fact" | "
|
|
1399
|
+
options: { types?: Array<"fact" | "topic" | "person" | "quote">; limit?: number } = {}
|
|
1317
1400
|
): Promise<{
|
|
1318
1401
|
facts: Fact[];
|
|
1319
|
-
traits: Trait[];
|
|
1320
1402
|
topics: Topic[];
|
|
1321
1403
|
people: Person[];
|
|
1322
1404
|
quotes: Quote[];
|
|
@@ -80,14 +80,13 @@ export async function filterHumanDataByVisibility(
|
|
|
80
80
|
const DEFAULT_GROUP = "General";
|
|
81
81
|
|
|
82
82
|
if (persona.id === "ei") {
|
|
83
|
-
const [facts,
|
|
83
|
+
const [facts, topics, people, quotes] = await Promise.all([
|
|
84
84
|
selectRelevantItems(human.facts, DATA_ITEM_LIMIT, currentMessage),
|
|
85
|
-
selectRelevantItems(human.traits, DATA_ITEM_LIMIT, currentMessage),
|
|
86
85
|
selectRelevantItems(human.topics, DATA_ITEM_LIMIT, currentMessage),
|
|
87
86
|
selectRelevantItems(human.people, DATA_ITEM_LIMIT, currentMessage),
|
|
88
87
|
selectRelevantQuotes(human.quotes ?? [], currentMessage),
|
|
89
88
|
]);
|
|
90
|
-
return { facts,
|
|
89
|
+
return { facts, topics, people, quotes };
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
const visibleGroups = new Set<string>();
|
|
@@ -109,15 +108,14 @@ export async function filterHumanDataByVisibility(
|
|
|
109
108
|
return effectiveGroups.some((g) => visibleGroups.has(g));
|
|
110
109
|
});
|
|
111
110
|
|
|
112
|
-
const [facts,
|
|
111
|
+
const [facts, topics, people, quotes] = await Promise.all([
|
|
113
112
|
selectRelevantItems(filterByGroup(human.facts), DATA_ITEM_LIMIT, currentMessage),
|
|
114
|
-
selectRelevantItems(filterByGroup(human.traits), DATA_ITEM_LIMIT, currentMessage),
|
|
115
113
|
selectRelevantItems(filterByGroup(human.topics), DATA_ITEM_LIMIT, currentMessage),
|
|
116
114
|
selectRelevantItems(filterByGroup(human.people), DATA_ITEM_LIMIT, currentMessage),
|
|
117
115
|
selectRelevantQuotes(groupFilteredQuotes, currentMessage),
|
|
118
116
|
]);
|
|
119
117
|
|
|
120
|
-
return { facts,
|
|
118
|
+
return { facts, topics, people, quotes };
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
// =============================================================================
|
package/src/core/state/human.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type { HumanEntity, Fact,
|
|
1
|
+
import type { HumanEntity, Fact, Topic, Person, Quote } from "../types.js";
|
|
2
2
|
|
|
3
3
|
export function createDefaultHumanEntity(): HumanEntity {
|
|
4
4
|
return {
|
|
5
5
|
entity: "human",
|
|
6
6
|
facts: [],
|
|
7
|
-
traits: [],
|
|
8
7
|
topics: [],
|
|
9
8
|
people: [],
|
|
10
9
|
quotes: [],
|
|
@@ -63,30 +62,6 @@ export class HumanState {
|
|
|
63
62
|
return false;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
trait_upsert(trait: Trait): void {
|
|
67
|
-
const idx = this.human.traits.findIndex((t) => t.id === trait.id);
|
|
68
|
-
trait.last_updated = new Date().toISOString();
|
|
69
|
-
if (idx >= 0) {
|
|
70
|
-
this.human.traits[idx] = trait;
|
|
71
|
-
} else {
|
|
72
|
-
this.human.traits.push(trait);
|
|
73
|
-
}
|
|
74
|
-
this.human.last_updated = new Date().toISOString();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
trait_remove(id: string): boolean {
|
|
78
|
-
const idx = this.human.traits.findIndex((t) => t.id === id);
|
|
79
|
-
if (idx >= 0) {
|
|
80
|
-
this.human.traits.splice(idx, 1);
|
|
81
|
-
// Clean up quote references
|
|
82
|
-
this.human.quotes.forEach((q) => {
|
|
83
|
-
q.data_item_ids = q.data_item_ids.filter((itemId) => itemId !== id);
|
|
84
|
-
});
|
|
85
|
-
this.human.last_updated = new Date().toISOString();
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
65
|
|
|
91
66
|
topic_upsert(topic: Topic): void {
|
|
92
67
|
const idx = this.human.topics.findIndex((t) => t.id === topic.id);
|
|
@@ -213,7 +213,7 @@ export class PersonaState {
|
|
|
213
213
|
return removed;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
messages_getUnextracted(personaId: string, flag: "f" | "
|
|
216
|
+
messages_getUnextracted(personaId: string, flag: "f" | "t" | "p" | "e", limit?: number): Message[] {
|
|
217
217
|
const data = this.personas.get(personaId);
|
|
218
218
|
if (!data) return [];
|
|
219
219
|
const unextracted = data.messages.filter(m => m[flag] !== true);
|
|
@@ -223,7 +223,7 @@ export class PersonaState {
|
|
|
223
223
|
return unextracted.map(m => ({ ...m }));
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
messages_markExtracted(personaId: string, messageIds: string[], flag: "f" | "
|
|
226
|
+
messages_markExtracted(personaId: string, messageIds: string[], flag: "f" | "t" | "p" | "e"): number {
|
|
227
227
|
const data = this.personas.get(personaId);
|
|
228
228
|
if (!data) return 0;
|
|
229
229
|
const idsSet = new Set(messageIds);
|
|
@@ -3,7 +3,6 @@ import type {
|
|
|
3
3
|
PersonaEntity,
|
|
4
4
|
Message,
|
|
5
5
|
Fact,
|
|
6
|
-
Trait,
|
|
7
6
|
Topic,
|
|
8
7
|
Person,
|
|
9
8
|
Quote,
|
|
@@ -14,6 +13,7 @@ import type {
|
|
|
14
13
|
ToolDefinition,
|
|
15
14
|
ToolProvider,
|
|
16
15
|
} from "./types.js";
|
|
16
|
+
import { BUILT_IN_FACT_NAMES } from './constants/built-in-facts.js';
|
|
17
17
|
import type { Storage } from "../storage/interface.js";
|
|
18
18
|
import {
|
|
19
19
|
HumanState,
|
|
@@ -43,6 +43,8 @@ export class StateManager {
|
|
|
43
43
|
this.tools = state.tools ?? [];
|
|
44
44
|
this.providers = state.providers ?? [];
|
|
45
45
|
this.migrateLearnedByToIds();
|
|
46
|
+
this.migrateFactValidation();
|
|
47
|
+
this.migrateMessageFlags();
|
|
46
48
|
} else {
|
|
47
49
|
this.humanState.load(createDefaultHumanEntity());
|
|
48
50
|
}
|
|
@@ -80,13 +82,114 @@ export class StateManager {
|
|
|
80
82
|
dirty = true;
|
|
81
83
|
}
|
|
82
84
|
};
|
|
83
|
-
[...human.facts, ...human.
|
|
85
|
+
[...human.facts, ...human.topics, ...human.people].forEach(migrateItem);
|
|
84
86
|
if (dirty) {
|
|
85
87
|
this.humanState.set(human);
|
|
86
88
|
console.log("[StateManager] Migrated learned_by fields from display names to persona IDs");
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Migration: Facts used to have a 'validated' field (now removed).
|
|
94
|
+
* Now, only 25 built-in facts remain; others are converted to Topics with category='Fact'.
|
|
95
|
+
* - Facts with 'validated' field whose name is NOT in BUILT_IN_FACT_NAMES → move to Topics
|
|
96
|
+
* - Facts with 'validated' field whose name IS in BUILT_IN_FACT_NAMES → strip 'validated'
|
|
97
|
+
* No-op for already-migrated data (no 'validated' field present).
|
|
98
|
+
*/
|
|
99
|
+
private migrateFactValidation(): void {
|
|
100
|
+
const human = this.humanState.get();
|
|
101
|
+
|
|
102
|
+
// Check if any fact has 'validated' property (old format detection)
|
|
103
|
+
const hasOldFormat = human.facts.some((f) => 'validated' in f);
|
|
104
|
+
if (!hasOldFormat) return;
|
|
105
|
+
|
|
106
|
+
let dirty = false;
|
|
107
|
+
const newFacts: Fact[] = [];
|
|
108
|
+
let movedCount = 0;
|
|
109
|
+
let strippedCount = 0;
|
|
110
|
+
// Define legacy fact interface for type-safe migration
|
|
111
|
+
interface LegacyFact extends Fact {
|
|
112
|
+
validated?: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const fact of human.facts) {
|
|
116
|
+
if (!('validated' in fact)) {
|
|
117
|
+
// Already migrated fact, keep as-is
|
|
118
|
+
newFacts.push(fact);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (BUILT_IN_FACT_NAMES.has(fact.name)) {
|
|
123
|
+
// Matching built-in: strip 'validated' field, preserve description
|
|
124
|
+
const { validated, ...cleanedFact } = fact as LegacyFact;
|
|
125
|
+
newFacts.push(cleanedFact);
|
|
126
|
+
strippedCount++;
|
|
127
|
+
dirty = true;
|
|
128
|
+
} else {
|
|
129
|
+
// Non-matching: move to Topics
|
|
130
|
+
const newTopic: Topic = {
|
|
131
|
+
id: crypto.randomUUID(),
|
|
132
|
+
name: fact.name,
|
|
133
|
+
description: fact.description,
|
|
134
|
+
category: 'Fact',
|
|
135
|
+
sentiment: fact.sentiment,
|
|
136
|
+
exposure_current: 0.3,
|
|
137
|
+
exposure_desired: 0.3,
|
|
138
|
+
last_updated: fact.last_updated,
|
|
139
|
+
learned_by: fact.learned_by,
|
|
140
|
+
last_changed_by: fact.last_changed_by,
|
|
141
|
+
persona_groups: fact.persona_groups,
|
|
142
|
+
embedding: fact.embedding,
|
|
143
|
+
};
|
|
144
|
+
human.topics.push(newTopic);
|
|
145
|
+
movedCount++;
|
|
146
|
+
dirty = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (dirty) {
|
|
151
|
+
human.facts = newFacts;
|
|
152
|
+
this.humanState.set(human);
|
|
153
|
+
console.log(
|
|
154
|
+
`[StateManager] Migrated fact validation: moved ${movedCount} non-matching facts to Topics, stripped 'validated' from ${strippedCount} built-in facts`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Migration: Message extraction flags were incorrectly named.
|
|
161
|
+
* Old: p=Topics, o=People, r=Traits (dead)
|
|
162
|
+
* New: t=Topics, p=People (r and o removed)
|
|
163
|
+
* Detects old format by presence of 'o' flag on any message.
|
|
164
|
+
*/
|
|
165
|
+
private migrateMessageFlags(): void {
|
|
166
|
+
const personas = this.personaState.getAll();
|
|
167
|
+
let migratedCount = 0;
|
|
168
|
+
|
|
169
|
+
for (const persona of personas) {
|
|
170
|
+
// Access raw message objects to detect and remap old flags
|
|
171
|
+
const rawMessages = (this.personaState as unknown as { personas: Map<string, { messages: Array<Record<string, unknown>> }> }).personas.get(persona.id)?.messages ?? [];
|
|
172
|
+
const hasOldFormat = rawMessages.some(m => 'o' in m || 'r' in m);
|
|
173
|
+
if (!hasOldFormat) continue;
|
|
174
|
+
|
|
175
|
+
for (const msg of rawMessages) {
|
|
176
|
+
// Remap: old p (topics) → new t; old o (people) → new p
|
|
177
|
+
const oldP = msg['p']; // was topics
|
|
178
|
+
const oldO = msg['o']; // was people
|
|
179
|
+
msg['t'] = oldP; // topics: old p → new t
|
|
180
|
+
msg['p'] = oldO; // people: old o → new p
|
|
181
|
+
delete msg['r']; // trait flag dead
|
|
182
|
+
delete msg['o']; // old people flag dead
|
|
183
|
+
migratedCount++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (migratedCount > 0) {
|
|
188
|
+
this.scheduleSave();
|
|
189
|
+
console.log(`[StateManager] Migrated message flags (p→t, o→p, removed r/o) for ${migratedCount} messages`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
90
193
|
/**
|
|
91
194
|
* Returns true if value looks like a persona ID (UUID or the special "ei" id).
|
|
92
195
|
* Display names are free-form strings that won't match UUID format.
|
|
@@ -132,16 +235,6 @@ export class StateManager {
|
|
|
132
235
|
return result;
|
|
133
236
|
}
|
|
134
237
|
|
|
135
|
-
human_trait_upsert(trait: Trait): void {
|
|
136
|
-
this.humanState.trait_upsert(trait);
|
|
137
|
-
this.scheduleSave();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
human_trait_remove(id: string): boolean {
|
|
141
|
-
const result = this.humanState.trait_remove(id);
|
|
142
|
-
this.scheduleSave();
|
|
143
|
-
return result;
|
|
144
|
-
}
|
|
145
238
|
|
|
146
239
|
human_topic_upsert(topic: Topic): void {
|
|
147
240
|
this.humanState.topic_upsert(topic);
|
|
@@ -303,11 +396,11 @@ export class StateManager {
|
|
|
303
396
|
return result;
|
|
304
397
|
}
|
|
305
398
|
|
|
306
|
-
messages_getUnextracted(personaId: string, flag: "f" | "
|
|
399
|
+
messages_getUnextracted(personaId: string, flag: "f" | "t" | "p" | "e", limit?: number): Message[] {
|
|
307
400
|
return this.personaState.messages_getUnextracted(personaId, flag, limit);
|
|
308
401
|
}
|
|
309
402
|
|
|
310
|
-
messages_markExtracted(personaId: string, messageIds: string[], flag: "f" | "
|
|
403
|
+
messages_markExtracted(personaId: string, messageIds: string[], flag: "f" | "t" | "p" | "e"): number {
|
|
311
404
|
const result = this.personaState.messages_markExtracted(personaId, messageIds, flag);
|
|
312
405
|
this.scheduleSave();
|
|
313
406
|
return result;
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* The searchHumanData function is injected at construction to avoid circular deps.
|
|
6
6
|
*/
|
|
7
7
|
import type { ToolExecutor } from "../types.js";
|
|
8
|
-
import type { Fact,
|
|
8
|
+
import type { Fact, Topic, Person, Quote } from "../../types.js";
|
|
9
9
|
|
|
10
10
|
type SearchHumanData = (
|
|
11
11
|
query: string,
|
|
12
|
-
options?: { types?: Array<"fact" | "
|
|
13
|
-
) => Promise<{ facts: Fact[];
|
|
12
|
+
options?: { types?: Array<"fact" | "topic" | "person" | "quote">; limit?: number }
|
|
13
|
+
) => Promise<{ facts: Fact[]; topics: Topic[]; people: Person[]; quotes: Quote[] }>;
|
|
14
14
|
|
|
15
15
|
export function createReadMemoryExecutor(searchHumanData: SearchHumanData): ToolExecutor {
|
|
16
16
|
return {
|
|
@@ -26,20 +26,19 @@ export function createReadMemoryExecutor(searchHumanData: SearchHumanData): Tool
|
|
|
26
26
|
|
|
27
27
|
const types = Array.isArray(args.types)
|
|
28
28
|
? (args.types.filter(
|
|
29
|
-
t => typeof t === "string" && ["fact", "
|
|
30
|
-
) as Array<"fact" | "
|
|
29
|
+
t => typeof t === "string" && ["fact", "topic", "person", "quote"].includes(t)
|
|
30
|
+
) as Array<"fact" | "topic" | "person" | "quote">)
|
|
31
31
|
: undefined;
|
|
32
32
|
|
|
33
33
|
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.min(args.limit, 20) : 10;
|
|
34
34
|
|
|
35
35
|
const results = await searchHumanData(query, { types, limit });
|
|
36
36
|
|
|
37
|
-
const total = results.facts.length + results.
|
|
38
|
-
console.log(`[read_memory] query="${query}" => ${total} results (facts=${results.facts.length},
|
|
37
|
+
const total = results.facts.length + results.topics.length + results.people.length + results.quotes.length;
|
|
38
|
+
console.log(`[read_memory] query="${query}" => ${total} results (facts=${results.facts.length}, topics=${results.topics.length}, people=${results.people.length}, quotes=${results.quotes.length})`);
|
|
39
39
|
|
|
40
40
|
const output: Record<string, unknown[]> = {};
|
|
41
41
|
if (results.facts.length > 0) output.facts = results.facts.map(f => ({ name: f.name, description: f.description }));
|
|
42
|
-
if (results.traits.length > 0) output.traits = results.traits.map(t => ({ name: t.name, description: t.description }));
|
|
43
42
|
if (results.topics.length > 0) output.topics = results.topics.map(t => ({ name: t.name, description: t.description }));
|
|
44
43
|
if (results.people.length > 0) output.people = results.people.map(p => ({ name: p.name, relationship: p.relationship, description: p.description }));
|
|
45
44
|
if (results.quotes.length > 0) output.quotes = results.quotes.map(q => ({ text: q.text, speaker: q.speaker }));
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Source of truth: CONTRACTS.md
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { ValidationLevel } from "./enums.js";
|
|
7
6
|
|
|
8
7
|
export interface DataItemBase {
|
|
9
8
|
id: string;
|
|
@@ -18,11 +17,10 @@ export interface DataItemBase {
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export interface Fact extends DataItemBase {
|
|
21
|
-
validated: ValidationLevel;
|
|
22
20
|
validated_date: string;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
export interface
|
|
23
|
+
export interface PersonaTrait extends DataItemBase {
|
|
26
24
|
strength?: number;
|
|
27
25
|
}
|
|
28
26
|
|
|
@@ -77,4 +75,4 @@ export interface Quote {
|
|
|
77
75
|
|
|
78
76
|
export type DataItemType = "fact" | "trait" | "topic" | "person";
|
|
79
77
|
|
|
80
|
-
export type DataItem = Fact |
|
|
78
|
+
export type DataItem = Fact | PersonaTrait | Topic | Person;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Source of truth: CONTRACTS.md
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { Fact,
|
|
6
|
+
import type { Fact, PersonaTrait, Topic, Person, Quote, PersonaTopic } from "./data-items.js";
|
|
7
7
|
import type { ProviderType } from "./enums.js";
|
|
8
8
|
|
|
9
9
|
export interface SyncCredentials {
|
|
@@ -14,6 +14,8 @@ export interface SyncCredentials {
|
|
|
14
14
|
export interface OpenCodeSettings {
|
|
15
15
|
integration?: boolean;
|
|
16
16
|
polling_interval_ms?: number; // Default: 1800000 (30 min)
|
|
17
|
+
extraction_model?: string; // "Provider:model" for extraction. Unset = uses default_model.
|
|
18
|
+
extraction_token_limit?: number; // Token budget for extraction chunking. Unset = resolved from model.
|
|
17
19
|
last_sync?: string; // ISO timestamp
|
|
18
20
|
extraction_point?: string; // ISO timestamp - cursor for single-session archive scan
|
|
19
21
|
processed_sessions?: Record<string, string>; // sessionId → ISO timestamp of last import
|
|
@@ -25,6 +27,7 @@ export interface CeremonyConfig {
|
|
|
25
27
|
decay_rate?: number; // Default: 0.1
|
|
26
28
|
explore_threshold?: number; // Default: 3
|
|
27
29
|
dedup_threshold?: number; // Cosine similarity threshold for dedup candidates. Default: 0.85
|
|
30
|
+
event_window_hours?: number; // Gap threshold for conversation window detection. Default: 8
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export interface BackupConfig {
|
|
@@ -88,7 +91,6 @@ export interface HumanSettings {
|
|
|
88
91
|
export interface HumanEntity {
|
|
89
92
|
entity: "human";
|
|
90
93
|
facts: Fact[];
|
|
91
|
-
traits: Trait[];
|
|
92
94
|
topics: Topic[];
|
|
93
95
|
people: Person[];
|
|
94
96
|
quotes: Quote[];
|
|
@@ -107,7 +109,7 @@ export interface PersonaEntity {
|
|
|
107
109
|
model?: string;
|
|
108
110
|
group_primary?: string | null;
|
|
109
111
|
groups_visible?: string[];
|
|
110
|
-
traits:
|
|
112
|
+
traits: PersonaTrait[];
|
|
111
113
|
topics: PersonaTopic[];
|
|
112
114
|
is_paused: boolean;
|
|
113
115
|
pause_until?: string;
|
|
@@ -129,7 +131,7 @@ export interface PersonaCreationInput {
|
|
|
129
131
|
aliases?: string[];
|
|
130
132
|
long_description?: string;
|
|
131
133
|
short_description?: string;
|
|
132
|
-
traits?: Partial<
|
|
134
|
+
traits?: Partial<PersonaTrait>[];
|
|
133
135
|
topics?: Partial<PersonaTopic>[];
|
|
134
136
|
model?: string;
|
|
135
137
|
group_primary?: string;
|
package/src/core/types/enums.ts
CHANGED
|
@@ -9,11 +9,6 @@ export enum ContextStatus {
|
|
|
9
9
|
Never = "never",
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export enum ValidationLevel {
|
|
13
|
-
None = "none", // Fresh data, never acknowledged
|
|
14
|
-
Ei = "ei", // Ei mentioned it to user (don't mention again)
|
|
15
|
-
Human = "human", // User explicitly confirmed (locked)
|
|
16
|
-
}
|
|
17
12
|
export enum LLMRequestType {
|
|
18
13
|
Response = "response",
|
|
19
14
|
JSON = "json",
|
|
@@ -30,12 +25,13 @@ export enum LLMNextStep {
|
|
|
30
25
|
HandlePersonaResponse = "handlePersonaResponse",
|
|
31
26
|
HandlePersonaGeneration = "handlePersonaGeneration",
|
|
32
27
|
HandlePersonaDescriptions = "handlePersonaDescriptions",
|
|
33
|
-
|
|
34
|
-
HandleHumanTraitScan = "handleHumanTraitScan",
|
|
28
|
+
HandleFactFind = "handleFactFind",
|
|
35
29
|
HandleHumanTopicScan = "handleHumanTopicScan",
|
|
36
30
|
HandleHumanPersonScan = "handleHumanPersonScan",
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
HandleTopicMatch = "handleTopicMatch",
|
|
32
|
+
HandleTopicUpdate = "handleTopicUpdate",
|
|
33
|
+
HandlePersonMatch = "handlePersonMatch",
|
|
34
|
+
HandlePersonUpdate = "handlePersonUpdate",
|
|
39
35
|
HandlePersonaTraitExtraction = "handlePersonaTraitExtraction",
|
|
40
36
|
HandlePersonaTopicScan = "handlePersonaTopicScan",
|
|
41
37
|
HandlePersonaTopicMatch = "handlePersonaTopicMatch",
|
|
@@ -54,6 +50,7 @@ export enum LLMNextStep {
|
|
|
54
50
|
HandleRewriteScan = "handleRewriteScan",
|
|
55
51
|
HandleRewriteRewrite = "handleRewriteRewrite",
|
|
56
52
|
HandleDedupCurate = "handleDedupCurate",
|
|
53
|
+
HandleEventScan = "handleEventScan",
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
export enum ProviderType {
|
package/src/core/types/llm.ts
CHANGED
|
@@ -18,9 +18,9 @@ export interface Message {
|
|
|
18
18
|
// Extraction completion flags (omit when false to save space)
|
|
19
19
|
// Single-letter names minimize storage overhead for large message histories
|
|
20
20
|
f?: boolean; // Fact extraction completed
|
|
21
|
-
|
|
21
|
+
t?: boolean; // Topic extraction completed
|
|
22
22
|
p?: boolean; // Person extraction completed
|
|
23
|
-
|
|
23
|
+
e?: boolean; // Event (epic) extraction completed
|
|
24
24
|
// Image generation fields (web-only, ephemeral)
|
|
25
25
|
_synthesis?: boolean; // True if message was created by multi-message synthesis
|
|
26
26
|
|