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,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();
|
|
@@ -291,7 +296,7 @@ export class Processor {
|
|
|
291
296
|
builtin: true,
|
|
292
297
|
enabled: true,
|
|
293
298
|
created_at: now,
|
|
294
|
-
max_calls_per_interaction: 3
|
|
299
|
+
max_calls_per_interaction: 6, // Dedup needs to verify relationships before irreversible merges. Typical cluster (3-8 items) requires: parent concept lookup + 2 relationship verifications + context validation. Still under HARD_TOOL_CALL_LIMIT (10).
|
|
295
300
|
});
|
|
296
301
|
}
|
|
297
302
|
|
|
@@ -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
|
|
|
@@ -715,14 +795,23 @@ const toolNextSteps = new Set([
|
|
|
715
795
|
LLMNextStep.HandleHeartbeatCheck,
|
|
716
796
|
LLMNextStep.HandleEiHeartbeat,
|
|
717
797
|
LLMNextStep.HandleToolContinuation,
|
|
798
|
+
LLMNextStep.HandleDedupCurate,
|
|
718
799
|
]);
|
|
719
800
|
const toolPersonaId =
|
|
720
801
|
personaId ??
|
|
721
802
|
(request.next_step === LLMNextStep.HandleEiHeartbeat ? "ei" : undefined);
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
803
|
+
|
|
804
|
+
// Dedup operates on Human data, not persona data - provide read_memory directly
|
|
805
|
+
let tools: ToolDefinition[] = [];
|
|
806
|
+
if (request.next_step === LLMNextStep.HandleDedupCurate) {
|
|
807
|
+
const readMemory = this.stateManager.tools_getByName("read_memory");
|
|
808
|
+
if (readMemory?.enabled) {
|
|
809
|
+
tools = [readMemory];
|
|
810
|
+
}
|
|
811
|
+
} else if (toolNextSteps.has(request.next_step) && toolPersonaId) {
|
|
812
|
+
tools = this.stateManager.tools_getForPersona(toolPersonaId, this.isTUI);
|
|
813
|
+
}
|
|
814
|
+
|
|
726
815
|
console.log(
|
|
727
816
|
`[Tools] Dispatch for ${request.next_step} persona=${toolPersonaId ?? "none"}: ${tools.length} tool(s) attached`
|
|
728
817
|
);
|
|
@@ -1084,7 +1173,10 @@ const toolNextSteps = new Set([
|
|
|
1084
1173
|
}
|
|
1085
1174
|
}
|
|
1086
1175
|
|
|
1087
|
-
if (
|
|
1176
|
+
if (
|
|
1177
|
+
response.request.next_step === LLMNextStep.HandleTopicUpdate ||
|
|
1178
|
+
response.request.next_step === LLMNextStep.HandlePersonUpdate
|
|
1179
|
+
) {
|
|
1088
1180
|
this.interface.onHumanUpdated?.();
|
|
1089
1181
|
this.interface.onQuoteAdded?.();
|
|
1090
1182
|
}
|
|
@@ -1093,6 +1185,10 @@ const toolNextSteps = new Set([
|
|
|
1093
1185
|
this.interface.onHumanUpdated?.();
|
|
1094
1186
|
}
|
|
1095
1187
|
|
|
1188
|
+
if (response.request.next_step === LLMNextStep.HandleFactFind) {
|
|
1189
|
+
this.interface.onHumanUpdated?.();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1096
1192
|
if (typeof response.request.data.ceremony_progress === "number") {
|
|
1097
1193
|
handleCeremonyProgress(this.stateManager, response.request.data.ceremony_progress);
|
|
1098
1194
|
}
|
|
@@ -1256,10 +1352,6 @@ const toolNextSteps = new Set([
|
|
|
1256
1352
|
this.interface.onHumanUpdated?.();
|
|
1257
1353
|
}
|
|
1258
1354
|
|
|
1259
|
-
async upsertTrait(trait: Trait): Promise<void> {
|
|
1260
|
-
await upsertTrait(this.stateManager, trait);
|
|
1261
|
-
this.interface.onHumanUpdated?.();
|
|
1262
|
-
}
|
|
1263
1355
|
|
|
1264
1356
|
async upsertTopic(topic: Topic): Promise<void> {
|
|
1265
1357
|
await upsertTopic(this.stateManager, topic);
|
|
@@ -1272,7 +1364,7 @@ const toolNextSteps = new Set([
|
|
|
1272
1364
|
}
|
|
1273
1365
|
|
|
1274
1366
|
async removeDataItem(
|
|
1275
|
-
type: "fact" | "
|
|
1367
|
+
type: "fact" | "topic" | "person",
|
|
1276
1368
|
id: string
|
|
1277
1369
|
): Promise<void> {
|
|
1278
1370
|
await removeDataItem(this.stateManager, type, id);
|
|
@@ -1304,10 +1396,9 @@ const toolNextSteps = new Set([
|
|
|
1304
1396
|
|
|
1305
1397
|
async searchHumanData(
|
|
1306
1398
|
query: string,
|
|
1307
|
-
options: { types?: Array<"fact" | "
|
|
1399
|
+
options: { types?: Array<"fact" | "topic" | "person" | "quote">; limit?: number } = {}
|
|
1308
1400
|
): Promise<{
|
|
1309
1401
|
facts: Fact[];
|
|
1310
|
-
traits: Trait[];
|
|
1311
1402
|
topics: Topic[];
|
|
1312
1403
|
people: Person[];
|
|
1313
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;
|