ei-tui 0.1.3 → 0.1.4
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 +31 -34
- package/package.json +5 -1
- package/src/README.md +85 -1
- package/src/cli/README.md +29 -21
- package/src/cli/retrieval.ts +5 -17
- package/src/cli.ts +69 -0
- package/src/core/handlers/index.ts +91 -158
- package/src/core/orchestrators/ceremony.ts +1 -1
- package/src/core/processor.ts +172 -45
- package/src/core/state/checkpoints.ts +4 -0
- package/src/core/state/queue.ts +1 -10
- package/src/core/state-manager.ts +1 -7
- package/src/core/types.ts +4 -5
- package/src/core/utils/crossFind.ts +44 -0
- package/src/core/utils/index.ts +4 -0
- package/src/integrations/opencode/importer.ts +117 -690
- package/src/prompts/heartbeat/ei.ts +61 -133
- package/src/prompts/heartbeat/types.ts +47 -17
- package/src/prompts/index.ts +2 -5
- package/tui/README.md +77 -3
- package/tui/src/commands/editor.tsx +1 -1
- package/tui/src/components/CommandSuggest.tsx +50 -0
- package/tui/src/components/PromptInput.tsx +111 -29
- package/tui/src/components/Sidebar.tsx +6 -2
- package/tui/src/context/ei.tsx +10 -0
- package/tui/src/context/keyboard.tsx +90 -2
- package/tui/src/util/clipboard.ts +73 -0
- package/tui/src/util/yaml-serializers.ts +12 -4
- package/src/prompts/validation/ei.ts +0 -93
- package/src/prompts/validation/index.ts +0 -6
- package/src/prompts/validation/types.ts +0 -22
package/src/core/processor.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
LLMRequestType,
|
|
3
3
|
LLMPriority,
|
|
4
4
|
LLMNextStep,
|
|
5
|
+
ValidationLevel,
|
|
5
6
|
RESERVED_PERSONA_NAMES,
|
|
6
7
|
isReservedPersonaName,
|
|
7
8
|
type LLMRequest,
|
|
@@ -35,9 +36,12 @@ import {
|
|
|
35
36
|
buildResponsePrompt,
|
|
36
37
|
buildPersonaTraitExtractionPrompt,
|
|
37
38
|
buildHeartbeatCheckPrompt,
|
|
39
|
+
buildEiHeartbeatPrompt,
|
|
38
40
|
type ResponsePromptData,
|
|
39
41
|
type PersonaTraitExtractionPromptData,
|
|
40
42
|
type HeartbeatCheckPromptData,
|
|
43
|
+
type EiHeartbeatPromptData,
|
|
44
|
+
type EiHeartbeatItem,
|
|
41
45
|
} from "../prompts/index.js";
|
|
42
46
|
import {
|
|
43
47
|
orchestratePersonaGeneration,
|
|
@@ -369,7 +373,7 @@ export class Processor {
|
|
|
369
373
|
|
|
370
374
|
const human = this.stateManager.getHuman();
|
|
371
375
|
|
|
372
|
-
if (this.isTUI && human.settings?.opencode?.integration) {
|
|
376
|
+
if (this.isTUI && human.settings?.opencode?.integration && this.stateManager.queue_length() === 0) {
|
|
373
377
|
await this.checkAndSyncOpenCode(human, now);
|
|
374
378
|
}
|
|
375
379
|
|
|
@@ -434,12 +438,10 @@ export class Processor {
|
|
|
434
438
|
},
|
|
435
439
|
});
|
|
436
440
|
|
|
437
|
-
const since = lastSync > 0 ? new Date(lastSync) : new Date(0);
|
|
438
|
-
|
|
439
441
|
this.openCodeImportInProgress = true;
|
|
440
442
|
import("../integrations/opencode/importer.js")
|
|
441
|
-
.then(({ importOpenCodeSessions }) =>
|
|
442
|
-
importOpenCodeSessions(
|
|
443
|
+
.then(({ importOpenCodeSessions }) =>
|
|
444
|
+
importOpenCodeSessions({
|
|
443
445
|
stateManager: this.stateManager,
|
|
444
446
|
interface: this.interface,
|
|
445
447
|
})
|
|
@@ -449,7 +451,7 @@ export class Processor {
|
|
|
449
451
|
console.log(
|
|
450
452
|
`[Processor] OpenCode sync complete: ${result.sessionsProcessed} sessions, ` +
|
|
451
453
|
`${result.topicsCreated} topics created, ${result.messagesImported} messages imported, ` +
|
|
452
|
-
`${result.
|
|
454
|
+
`${result.extractionScansQueued} extraction scans queued`
|
|
453
455
|
);
|
|
454
456
|
}
|
|
455
457
|
})
|
|
@@ -491,20 +493,20 @@ export class Processor {
|
|
|
491
493
|
private async queueHeartbeatCheck(personaId: string): Promise<void> {
|
|
492
494
|
const persona = this.stateManager.persona_getById(personaId);
|
|
493
495
|
if (!persona) return;
|
|
494
|
-
|
|
495
496
|
this.stateManager.persona_update(personaId, { last_heartbeat: new Date().toISOString() });
|
|
496
|
-
|
|
497
497
|
const human = this.stateManager.getHuman();
|
|
498
498
|
const history = this.stateManager.messages_get(personaId);
|
|
499
|
-
|
|
499
|
+
if (personaId === "ei") {
|
|
500
|
+
await this.queueEiHeartbeat(human, history);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
500
503
|
|
|
504
|
+
const filteredHuman = await this.filterHumanDataByVisibility(human, persona);
|
|
501
505
|
const inactiveDays = persona.last_activity
|
|
502
506
|
? Math.floor((Date.now() - new Date(persona.last_activity).getTime()) / (1000 * 60 * 60 * 24))
|
|
503
507
|
: 0;
|
|
504
|
-
|
|
505
508
|
const sortByEngagementGap = <T extends { exposure_desired: number; exposure_current: number }>(items: T[]): T[] =>
|
|
506
509
|
[...items].sort((a, b) => (b.exposure_desired - b.exposure_current) - (a.exposure_desired - a.exposure_current));
|
|
507
|
-
|
|
508
510
|
const promptData: HeartbeatCheckPromptData = {
|
|
509
511
|
persona: {
|
|
510
512
|
name: persona.display_name,
|
|
@@ -532,6 +534,118 @@ export class Processor {
|
|
|
532
534
|
});
|
|
533
535
|
}
|
|
534
536
|
|
|
537
|
+
private async queueEiHeartbeat(human: HumanEntity, history: import("./types.js").Message[]): Promise<void> {
|
|
538
|
+
const now = Date.now();
|
|
539
|
+
const engagementGapThreshold = 0.2;
|
|
540
|
+
const cooldownMs = 7 * 24 * 60 * 60 * 1000;
|
|
541
|
+
const personas = this.stateManager.persona_getAll();
|
|
542
|
+
const items: EiHeartbeatItem[] = [];
|
|
543
|
+
|
|
544
|
+
const unverifiedFacts = human.facts
|
|
545
|
+
.filter(f => f.validated === ValidationLevel.None && f.learned_by !== "ei")
|
|
546
|
+
.slice(0, 5);
|
|
547
|
+
for (const fact of unverifiedFacts) {
|
|
548
|
+
const quote = human.quotes.find(q => q.data_item_ids.includes(fact.id));
|
|
549
|
+
items.push({
|
|
550
|
+
id: fact.id,
|
|
551
|
+
type: "Fact Check",
|
|
552
|
+
name: fact.name,
|
|
553
|
+
description: fact.description,
|
|
554
|
+
quote: quote?.text,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const underEngagedPeople = human.people
|
|
559
|
+
.filter(p =>
|
|
560
|
+
(p.exposure_desired - p.exposure_current) > engagementGapThreshold &&
|
|
561
|
+
(!p.last_ei_asked || now - new Date(p.last_ei_asked).getTime() > cooldownMs)
|
|
562
|
+
)
|
|
563
|
+
.sort((a, b) => (b.exposure_desired - b.exposure_current) - (a.exposure_desired - a.exposure_current))
|
|
564
|
+
.slice(0, 5);
|
|
565
|
+
for (const person of underEngagedPeople) {
|
|
566
|
+
const gap = Math.round((person.exposure_desired - person.exposure_current) * 100);
|
|
567
|
+
const quote = human.quotes.find(q => q.data_item_ids.includes(person.id));
|
|
568
|
+
items.push({
|
|
569
|
+
id: person.id,
|
|
570
|
+
type: "Low-Engagement Person",
|
|
571
|
+
engagement_delta: `${gap}%`,
|
|
572
|
+
relationship: person.relationship,
|
|
573
|
+
name: person.name,
|
|
574
|
+
description: person.description,
|
|
575
|
+
quote: quote?.text,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const underEngagedTopics = human.topics
|
|
580
|
+
.filter(t =>
|
|
581
|
+
(t.exposure_desired - t.exposure_current) > engagementGapThreshold &&
|
|
582
|
+
(!t.last_ei_asked || now - new Date(t.last_ei_asked).getTime() > cooldownMs)
|
|
583
|
+
)
|
|
584
|
+
.sort((a, b) => (b.exposure_desired - b.exposure_current) - (a.exposure_desired - a.exposure_current))
|
|
585
|
+
.slice(0, 5);
|
|
586
|
+
for (const topic of underEngagedTopics) {
|
|
587
|
+
const gap = Math.round((topic.exposure_desired - topic.exposure_current) * 100);
|
|
588
|
+
const quote = human.quotes.find(q => q.data_item_ids.includes(topic.id));
|
|
589
|
+
items.push({
|
|
590
|
+
id: topic.id,
|
|
591
|
+
type: "Low-Engagement Topic",
|
|
592
|
+
engagement_delta: `${gap}%`,
|
|
593
|
+
name: topic.name,
|
|
594
|
+
description: topic.description,
|
|
595
|
+
quote: quote?.text,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const activePersonas = personas
|
|
600
|
+
.filter(p => !p.is_archived && !p.is_paused && p.id !== "ei")
|
|
601
|
+
.map(p => {
|
|
602
|
+
const msgs = this.stateManager.messages_get(p.id);
|
|
603
|
+
const lastHuman = [...msgs].reverse().find(m => m.role === "human");
|
|
604
|
+
const lastTs = lastHuman?.timestamp ? new Date(lastHuman.timestamp).getTime() : 0;
|
|
605
|
+
return { persona: p, lastHumanTs: lastTs };
|
|
606
|
+
})
|
|
607
|
+
.filter(({ lastHumanTs }) => {
|
|
608
|
+
const daysSince = (now - lastHumanTs) / (1000 * 60 * 60 * 24);
|
|
609
|
+
return daysSince >= 3;
|
|
610
|
+
})
|
|
611
|
+
.sort((a, b) => a.lastHumanTs - b.lastHumanTs)
|
|
612
|
+
.slice(0, 3);
|
|
613
|
+
for (const { persona: p, lastHumanTs } of activePersonas) {
|
|
614
|
+
const daysSince = lastHumanTs > 0
|
|
615
|
+
? Math.floor((now - lastHumanTs) / (1000 * 60 * 60 * 24))
|
|
616
|
+
: 999;
|
|
617
|
+
items.push({
|
|
618
|
+
id: p.id,
|
|
619
|
+
type: "Inactive Persona",
|
|
620
|
+
name: p.display_name,
|
|
621
|
+
short_description: p.short_description,
|
|
622
|
+
days_inactive: daysSince,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (items.length === 0) {
|
|
627
|
+
console.log("[queueEiHeartbeat] No items to address, skipping");
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const promptData: EiHeartbeatPromptData = {
|
|
632
|
+
items,
|
|
633
|
+
recent_history: history.slice(-10),
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const prompt = buildEiHeartbeatPrompt(promptData);
|
|
637
|
+
|
|
638
|
+
this.stateManager.queue_enqueue({
|
|
639
|
+
type: LLMRequestType.JSON,
|
|
640
|
+
priority: LLMPriority.Low,
|
|
641
|
+
system: prompt.system,
|
|
642
|
+
user: prompt.user,
|
|
643
|
+
next_step: LLMNextStep.HandleEiHeartbeat,
|
|
644
|
+
model: this.getModelForPersona("ei"),
|
|
645
|
+
data: { personaId: "ei", isTUI: this.isTUI },
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
535
649
|
|
|
536
650
|
private classifyLLMError(error: string): string {
|
|
537
651
|
const match = error.match(/\((\d{3})\)/);
|
|
@@ -627,9 +741,7 @@ export class Processor {
|
|
|
627
741
|
}
|
|
628
742
|
}
|
|
629
743
|
|
|
630
|
-
|
|
631
|
-
this.interface.onHumanUpdated?.();
|
|
632
|
-
}
|
|
744
|
+
|
|
633
745
|
|
|
634
746
|
if (response.request.next_step === LLMNextStep.HandleHumanItemUpdate) {
|
|
635
747
|
this.interface.onHumanUpdated?.();
|
|
@@ -813,23 +925,16 @@ export class Processor {
|
|
|
813
925
|
async recallPendingMessages(personaId: string): Promise<string> {
|
|
814
926
|
const persona = this.stateManager.persona_getById(personaId);
|
|
815
927
|
if (!persona) return "";
|
|
816
|
-
|
|
817
928
|
this.clearPendingRequestsFor(personaId);
|
|
818
|
-
this.stateManager.queue_pause();
|
|
819
|
-
|
|
820
929
|
const messages = this.stateManager.messages_get(personaId);
|
|
821
930
|
const pendingIds = messages
|
|
822
931
|
.filter(m => m.role === "human" && !m.read)
|
|
823
932
|
.map(m => m.id);
|
|
824
|
-
|
|
825
933
|
if (pendingIds.length === 0) return "";
|
|
826
|
-
|
|
827
934
|
const removed = this.stateManager.messages_remove(personaId, pendingIds);
|
|
828
935
|
const recalledContent = removed.map(m => m.content).join("\n\n");
|
|
829
|
-
|
|
830
936
|
this.interface.onMessageAdded?.(personaId);
|
|
831
937
|
this.interface.onMessageRecalled?.(personaId, recalledContent);
|
|
832
|
-
|
|
833
938
|
return recalledContent;
|
|
834
939
|
}
|
|
835
940
|
|
|
@@ -987,13 +1092,40 @@ export class Processor {
|
|
|
987
1092
|
): Promise<ResponsePromptData["human"]> {
|
|
988
1093
|
const DEFAULT_GROUP = "General";
|
|
989
1094
|
const QUOTE_LIMIT = 10;
|
|
1095
|
+
const DATA_ITEM_LIMIT = 15;
|
|
990
1096
|
const SIMILARITY_THRESHOLD = 0.3;
|
|
1097
|
+
// Generic relevance selector for embedding-capable items.
|
|
1098
|
+
// Falls back to returning all items when no message/embeddings are available.
|
|
1099
|
+
const selectRelevantItems = async <T extends { id: string; embedding?: number[] }>(
|
|
1100
|
+
items: T[],
|
|
1101
|
+
limit: number
|
|
1102
|
+
): Promise<T[]> => {
|
|
1103
|
+
if (items.length === 0) return [];
|
|
1104
|
+
|
|
1105
|
+
const withEmbeddings = items.filter(i => i.embedding?.length);
|
|
1106
|
+
|
|
1107
|
+
if (currentMessage && withEmbeddings.length > 0) {
|
|
1108
|
+
try {
|
|
1109
|
+
const embeddingService = getEmbeddingService();
|
|
1110
|
+
const queryVector = await embeddingService.embed(currentMessage);
|
|
1111
|
+
const results = findTopK(queryVector, withEmbeddings, limit);
|
|
1112
|
+
const relevant = results
|
|
1113
|
+
.filter(({ similarity }) => similarity >= SIMILARITY_THRESHOLD)
|
|
1114
|
+
.map(({ item }) => item);
|
|
991
1115
|
|
|
1116
|
+
if (relevant.length > 0) return relevant;
|
|
1117
|
+
} catch (err) {
|
|
1118
|
+
console.warn("[filterHumanDataByVisibility] Embedding search failed:", err);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Fallback: return all items (caller may apply its own limit)
|
|
1123
|
+
return items;
|
|
1124
|
+
};
|
|
992
1125
|
const selectRelevantQuotes = async (quotes: Quote[]): Promise<Quote[]> => {
|
|
993
1126
|
if (quotes.length === 0) return [];
|
|
994
|
-
|
|
995
1127
|
const withEmbeddings = quotes.filter(q => q.embedding?.length);
|
|
996
|
-
|
|
1128
|
+
|
|
997
1129
|
if (currentMessage && withEmbeddings.length > 0) {
|
|
998
1130
|
try {
|
|
999
1131
|
const embeddingService = getEmbeddingService();
|
|
@@ -1002,35 +1134,31 @@ export class Processor {
|
|
|
1002
1134
|
const relevant = results
|
|
1003
1135
|
.filter(({ similarity }) => similarity >= SIMILARITY_THRESHOLD)
|
|
1004
1136
|
.map(({ item }) => item);
|
|
1005
|
-
|
|
1137
|
+
|
|
1006
1138
|
if (relevant.length > 0) return relevant;
|
|
1007
1139
|
} catch (err) {
|
|
1008
1140
|
console.warn("[filterHumanDataByVisibility] Embedding search failed:", err);
|
|
1009
1141
|
}
|
|
1010
1142
|
}
|
|
1011
|
-
|
|
1012
1143
|
return [...quotes]
|
|
1013
1144
|
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
1014
1145
|
.slice(0, QUOTE_LIMIT);
|
|
1015
1146
|
};
|
|
1016
|
-
|
|
1017
1147
|
if (persona.id === "ei") {
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
};
|
|
1148
|
+
const [facts, traits, topics, people, quotes] = await Promise.all([
|
|
1149
|
+
selectRelevantItems(human.facts, DATA_ITEM_LIMIT),
|
|
1150
|
+
selectRelevantItems(human.traits, DATA_ITEM_LIMIT),
|
|
1151
|
+
selectRelevantItems(human.topics, DATA_ITEM_LIMIT),
|
|
1152
|
+
selectRelevantItems(human.people, DATA_ITEM_LIMIT),
|
|
1153
|
+
selectRelevantQuotes(human.quotes ?? []),
|
|
1154
|
+
]);
|
|
1155
|
+
return { facts, traits, topics, people, quotes };
|
|
1026
1156
|
}
|
|
1027
|
-
|
|
1028
1157
|
const visibleGroups = new Set<string>();
|
|
1029
1158
|
if (persona.group_primary) {
|
|
1030
1159
|
visibleGroups.add(persona.group_primary);
|
|
1031
1160
|
}
|
|
1032
1161
|
(persona.groups_visible ?? []).forEach((g) => visibleGroups.add(g));
|
|
1033
|
-
|
|
1034
1162
|
const filterByGroup = <T extends DataItemBase>(items: T[]): T[] => {
|
|
1035
1163
|
return items.filter((item) => {
|
|
1036
1164
|
const itemGroups = item.persona_groups ?? [];
|
|
@@ -1038,21 +1166,20 @@ export class Processor {
|
|
|
1038
1166
|
return effectiveGroups.some((g) => visibleGroups.has(g));
|
|
1039
1167
|
});
|
|
1040
1168
|
};
|
|
1041
|
-
|
|
1042
1169
|
const groupFilteredQuotes = (human.quotes ?? []).filter((q) => {
|
|
1043
1170
|
const effectiveGroups = q.persona_groups.length === 0 ? [DEFAULT_GROUP] : q.persona_groups;
|
|
1044
1171
|
return effectiveGroups.some((g) => visibleGroups.has(g));
|
|
1045
1172
|
});
|
|
1046
1173
|
|
|
1047
|
-
const
|
|
1174
|
+
const [facts, traits, topics, people, quotes] = await Promise.all([
|
|
1175
|
+
selectRelevantItems(filterByGroup(human.facts), DATA_ITEM_LIMIT),
|
|
1176
|
+
selectRelevantItems(filterByGroup(human.traits), DATA_ITEM_LIMIT),
|
|
1177
|
+
selectRelevantItems(filterByGroup(human.topics), DATA_ITEM_LIMIT),
|
|
1178
|
+
selectRelevantItems(filterByGroup(human.people), DATA_ITEM_LIMIT),
|
|
1179
|
+
selectRelevantQuotes(groupFilteredQuotes),
|
|
1180
|
+
]);
|
|
1048
1181
|
|
|
1049
|
-
return {
|
|
1050
|
-
facts: filterByGroup(human.facts),
|
|
1051
|
-
traits: filterByGroup(human.traits),
|
|
1052
|
-
topics: filterByGroup(human.topics),
|
|
1053
|
-
people: filterByGroup(human.people),
|
|
1054
|
-
quotes: relevantQuotes,
|
|
1055
|
-
};
|
|
1182
|
+
return { facts, traits, topics, people, quotes };
|
|
1056
1183
|
}
|
|
1057
1184
|
|
|
1058
1185
|
private getVisiblePersonas(
|
package/src/core/state/queue.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LLMRequest,
|
|
1
|
+
import type { LLMRequest, QueueFailResult } from "../types.js";
|
|
2
2
|
|
|
3
3
|
const BASE_BACKOFF_MS = 2_000;
|
|
4
4
|
const MAX_BACKOFF_MS = 30_000;
|
|
@@ -93,16 +93,7 @@ export class QueueState {
|
|
|
93
93
|
return { dropped: false, retryDelay: delay };
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
getValidations(): LLMRequest[] {
|
|
97
|
-
return this.queue.filter(
|
|
98
|
-
(r) => r.next_step === ("handleEiValidation" as LLMNextStep)
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
96
|
|
|
102
|
-
clearValidations(ids: string[]): void {
|
|
103
|
-
const idSet = new Set(ids);
|
|
104
|
-
this.queue = this.queue.filter((r) => !idSet.has(r.id));
|
|
105
|
-
}
|
|
106
97
|
|
|
107
98
|
clearPersonaResponses(personaId: string, nextStep: string): string[] {
|
|
108
99
|
const removedIds: string[] = [];
|
|
@@ -275,14 +275,7 @@ export class StateManager {
|
|
|
275
275
|
return result;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
queue_getValidations(): LLMRequest[] {
|
|
279
|
-
return this.queueState.getValidations();
|
|
280
|
-
}
|
|
281
278
|
|
|
282
|
-
queue_clearValidations(ids: string[]): void {
|
|
283
|
-
this.queueState.clearValidations(ids);
|
|
284
|
-
this.scheduleSave();
|
|
285
|
-
}
|
|
286
279
|
|
|
287
280
|
queue_clearPersonaResponses(personaId: string, nextStep: string): string[] {
|
|
288
281
|
const result = this.queueState.clearPersonaResponses(personaId, nextStep);
|
|
@@ -338,6 +331,7 @@ export class StateManager {
|
|
|
338
331
|
this.humanState.load(state.human);
|
|
339
332
|
this.personaState.load(state.personas);
|
|
340
333
|
this.queueState.load(state.queue);
|
|
334
|
+
this.persistenceState.markExistingData();
|
|
341
335
|
this.scheduleSave();
|
|
342
336
|
}
|
|
343
337
|
|
package/src/core/types.ts
CHANGED
|
@@ -18,7 +18,6 @@ export enum ValidationLevel {
|
|
|
18
18
|
Ei = "ei", // Ei mentioned it to user (don't mention again)
|
|
19
19
|
Human = "human", // User explicitly confirmed (locked)
|
|
20
20
|
}
|
|
21
|
-
|
|
22
21
|
export enum LLMRequestType {
|
|
23
22
|
Response = "response",
|
|
24
23
|
JSON = "json",
|
|
@@ -47,9 +46,7 @@ export enum LLMNextStep {
|
|
|
47
46
|
HandlePersonaTopicUpdate = "handlePersonaTopicUpdate",
|
|
48
47
|
HandleHeartbeatCheck = "handleHeartbeatCheck",
|
|
49
48
|
HandleEiHeartbeat = "handleEiHeartbeat",
|
|
50
|
-
HandleEiValidation = "handleEiValidation",
|
|
51
49
|
HandleOneShot = "handleOneShot",
|
|
52
|
-
// Ceremony handlers
|
|
53
50
|
HandlePersonaExpire = "handlePersonaExpire",
|
|
54
51
|
HandlePersonaExplore = "handlePersonaExplore",
|
|
55
52
|
HandleDescriptionCheck = "handleDescriptionCheck",
|
|
@@ -83,6 +80,7 @@ export interface Topic extends DataItemBase {
|
|
|
83
80
|
category?: string; // Interest, Goal, Dream, Conflict, Concern, Fear, Hope, Plan, Project
|
|
84
81
|
exposure_current: number;
|
|
85
82
|
exposure_desired: number;
|
|
83
|
+
last_ei_asked?: string | null; // ISO timestamp of last time Ei proactively asked about this
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
/**
|
|
@@ -109,6 +107,7 @@ export interface Person extends DataItemBase {
|
|
|
109
107
|
relationship: string;
|
|
110
108
|
exposure_current: number;
|
|
111
109
|
exposure_desired: number;
|
|
110
|
+
last_ei_asked?: string | null; // ISO timestamp of last time Ei proactively asked about this
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
export interface Quote {
|
|
@@ -180,7 +179,8 @@ export interface OpenCodeSettings {
|
|
|
180
179
|
integration?: boolean;
|
|
181
180
|
polling_interval_ms?: number; // Default: 1800000 (30 min)
|
|
182
181
|
last_sync?: string; // ISO timestamp
|
|
183
|
-
extraction_point?: string; // ISO timestamp -
|
|
182
|
+
extraction_point?: string; // ISO timestamp - cursor for single-session archive scan
|
|
183
|
+
processed_sessions?: Record<string, string>; // sessionId → ISO timestamp of last import
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
export interface CeremonyConfig {
|
|
@@ -238,7 +238,6 @@ export interface PersonaEntity {
|
|
|
238
238
|
last_activity: string;
|
|
239
239
|
last_heartbeat?: string;
|
|
240
240
|
last_extraction?: string;
|
|
241
|
-
last_inactivity_ping?: string;
|
|
242
241
|
}
|
|
243
242
|
|
|
244
243
|
export interface PersonaCreationInput {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { HumanEntity, PersonaEntity, Fact, Trait, Topic, Person, Quote, PersonaTopic } from "../types.ts";
|
|
2
|
+
export type CrossFindResult =
|
|
3
|
+
| { type: "fact" } & Fact
|
|
4
|
+
| { type: "trait" } & Trait
|
|
5
|
+
| { type: "topic" } & Topic
|
|
6
|
+
| { type: "person" } & Person
|
|
7
|
+
| { type: "quote" } & Quote
|
|
8
|
+
| { type: "persona" } & PersonaEntity
|
|
9
|
+
| { type: "personaTopic"; personaId: string } & PersonaTopic
|
|
10
|
+
| { type: "personaTrait"; personaId: string } & Trait;
|
|
11
|
+
|
|
12
|
+
export function crossFind(
|
|
13
|
+
id: string,
|
|
14
|
+
human: HumanEntity,
|
|
15
|
+
personas?: PersonaEntity[],
|
|
16
|
+
): CrossFindResult | null {
|
|
17
|
+
|
|
18
|
+
const fact = human.facts.find(f => f.id === id);
|
|
19
|
+
if (fact) return { type: "fact", ...fact };
|
|
20
|
+
|
|
21
|
+
const trait = human.traits.find(t => t.id === id);
|
|
22
|
+
if (trait) return { type: "trait", ...trait };
|
|
23
|
+
|
|
24
|
+
const person = human.people.find(p => p.id === id);
|
|
25
|
+
if (person) return { type: "person", ...person };
|
|
26
|
+
|
|
27
|
+
const topic = human.topics.find(t => t.id === id);
|
|
28
|
+
if (topic) return { type: "topic", ...topic };
|
|
29
|
+
|
|
30
|
+
const quote = human.quotes.find(q => q.id === id);
|
|
31
|
+
if (quote) return { type: "quote", ...quote };
|
|
32
|
+
|
|
33
|
+
for (const persona of personas ?? []) {
|
|
34
|
+
if (persona.id === id) return { type: "persona", ...persona };
|
|
35
|
+
|
|
36
|
+
const pTopic = persona.topics.find(t => t.id === id);
|
|
37
|
+
if (pTopic) return { type: "personaTopic", personaId: persona.id, ...pTopic };
|
|
38
|
+
|
|
39
|
+
const pTrait = persona.traits.find(t => t.id === id);
|
|
40
|
+
if (pTrait) return { type: "personaTrait", personaId: persona.id, ...pTrait };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null;
|
|
44
|
+
}
|