ei-tui 0.5.4 → 0.6.1
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/package.json +1 -1
- package/src/core/constants/built-in-identifier-types.ts +24 -0
- package/src/core/embedding-service.ts +24 -1
- package/src/core/handlers/dedup.ts +34 -4
- package/src/core/handlers/heartbeat.ts +16 -0
- package/src/core/handlers/human-extraction.ts +201 -7
- package/src/core/handlers/human-matching.ts +71 -22
- package/src/core/handlers/index.ts +52 -14
- package/src/core/handlers/persona-generation.ts +2 -0
- package/src/core/handlers/persona-response.ts +37 -22
- package/src/core/handlers/persona-topics.ts +35 -271
- package/src/core/handlers/rewrite.ts +3 -0
- package/src/core/handlers/rooms.ts +41 -20
- package/src/core/handlers/utils.ts +10 -8
- package/src/core/heartbeat-manager.ts +60 -2
- package/src/core/llm-client.ts +1 -1
- package/src/core/message-manager.ts +3 -2
- package/src/core/orchestrators/ceremony.ts +54 -144
- package/src/core/orchestrators/dedup-phase.ts +0 -199
- package/src/core/orchestrators/extraction-chunker.ts +8 -3
- package/src/core/orchestrators/human-extraction.ts +37 -85
- package/src/core/orchestrators/index.ts +4 -8
- package/src/core/orchestrators/person-migration.ts +55 -0
- package/src/core/orchestrators/persona-topics.ts +64 -89
- package/src/core/orchestrators/room-extraction.ts +34 -0
- package/src/core/persona-manager.ts +21 -2
- package/src/core/personas/opencode-agent.ts +1 -0
- package/src/core/processor.ts +51 -14
- package/src/core/prompt-context-builder.ts +38 -5
- package/src/core/queue-processor.ts +4 -2
- package/src/core/room-manager.ts +6 -7
- package/src/core/state/human.ts +6 -0
- package/src/core/state/personas.ts +35 -10
- package/src/core/state/rooms.ts +21 -0
- package/src/core/state-manager.ts +61 -0
- package/src/core/types/data-items.ts +12 -0
- package/src/core/types/entities.ts +3 -0
- package/src/core/types/enums.ts +2 -7
- package/src/core/types/llm.ts +2 -0
- package/src/core/types/rooms.ts +2 -0
- package/src/core/utils/identifier-utils.ts +19 -0
- package/src/core/utils/index.ts +2 -1
- package/src/core/utils/levenshtein.ts +18 -0
- package/src/integrations/claude-code/importer.ts +1 -0
- package/src/integrations/cursor/importer.ts +1 -0
- package/src/prompts/ceremony/index.ts +1 -0
- package/src/prompts/ceremony/person-migration.ts +77 -0
- package/src/prompts/ceremony/rewrite.ts +1 -1
- package/src/prompts/ceremony/user-dedup.ts +15 -1
- package/src/prompts/heartbeat/check.ts +28 -12
- package/src/prompts/heartbeat/ei.ts +2 -0
- package/src/prompts/heartbeat/types.ts +12 -0
- package/src/prompts/human/index.ts +0 -2
- package/src/prompts/human/person-scan.ts +58 -14
- package/src/prompts/human/person-update.ts +171 -96
- package/src/prompts/human/topic-update.ts +1 -1
- package/src/prompts/human/types.ts +5 -1
- package/src/prompts/index.ts +3 -10
- package/src/prompts/message-utils.ts +9 -23
- package/src/prompts/persona/index.ts +3 -10
- package/src/prompts/persona/topics-rate.ts +95 -0
- package/src/prompts/persona/types.ts +8 -48
- package/src/prompts/response/index.ts +3 -7
- package/src/prompts/response/sections.ts +7 -57
- package/src/prompts/room/index.ts +1 -1
- package/src/prompts/room/sections.ts +8 -31
- package/tui/src/commands/me.tsx +14 -7
- package/tui/src/commands/persona.tsx +120 -83
- package/tui/src/components/MessageList.tsx +9 -4
- package/tui/src/components/RoomMessageList.tsx +10 -5
- package/tui/src/context/keyboard.tsx +2 -2
- package/tui/src/util/cyp-editor.tsx +13 -8
- package/tui/src/util/yaml-context.ts +66 -0
- package/tui/src/util/yaml-human.ts +274 -0
- package/tui/src/util/yaml-persona.ts +479 -0
- package/tui/src/util/yaml-provider.ts +215 -0
- package/tui/src/util/yaml-queue.ts +81 -0
- package/tui/src/util/yaml-quotes.ts +46 -0
- package/tui/src/util/yaml-serializers.ts +9 -1417
- package/tui/src/util/yaml-settings.ts +223 -0
- package/tui/src/util/yaml-shared.ts +32 -0
- package/tui/src/util/yaml-toolkit.ts +55 -0
- package/src/prompts/human/person-match.ts +0 -65
- package/src/prompts/persona/topics-match.ts +0 -70
- package/src/prompts/persona/topics-scan.ts +0 -98
- package/src/prompts/persona/topics-update.ts +0 -154
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LLMRequestType, LLMPriority, LLMNextStep, type Message, type Topic, type Person } from "../types.js";
|
|
2
|
+
import type { PersonIdentifier } from "../types/data-items.js";
|
|
2
3
|
import type { StateManager } from "../state-manager.js";
|
|
3
4
|
import {
|
|
4
5
|
buildFactFindPrompt,
|
|
@@ -6,16 +7,14 @@ import {
|
|
|
6
7
|
buildHumanPersonScanPrompt,
|
|
7
8
|
buildTopicMatchPrompt,
|
|
8
9
|
buildTopicUpdatePrompt,
|
|
9
|
-
buildPersonMatchPrompt,
|
|
10
10
|
buildPersonUpdatePrompt,
|
|
11
11
|
buildEventScanPrompt,
|
|
12
12
|
type TopicScanCandidate,
|
|
13
|
-
type PersonScanCandidate,
|
|
14
13
|
type ItemMatchResult,
|
|
15
14
|
type ParticipantContext,
|
|
16
15
|
} from "../../prompts/human/index.js";
|
|
17
16
|
import { chunkExtractionContext } from "./extraction-chunker.js";
|
|
18
|
-
import { getEmbeddingService, findTopK, getTopicEmbeddingText
|
|
17
|
+
import { getEmbeddingService, findTopK, getTopicEmbeddingText } from "../embedding-service.js";
|
|
19
18
|
import { resolveTokenLimit } from "../llm-client.js";
|
|
20
19
|
import { BUILT_IN_FACT_NAMES } from "../constants/built-in-facts.js";
|
|
21
20
|
import { buildEventWindows } from "../utils/event-windows.js";
|
|
@@ -192,11 +191,20 @@ export function queuePersonScan(context: ExtractionContext, state: StateManager,
|
|
|
192
191
|
state.messages_markExtracted(chunk.personaId, chunk.messages_analyze.map(m => m.id), "p");
|
|
193
192
|
}
|
|
194
193
|
|
|
194
|
+
const humanForScan = state.getHuman();
|
|
195
|
+
const userIdentifierTypesForScan = [...new Set(
|
|
196
|
+
humanForScan.people
|
|
197
|
+
.flatMap(p => (p.identifiers ?? []).map(i => i.type))
|
|
198
|
+
.filter(Boolean)
|
|
199
|
+
)];
|
|
200
|
+
|
|
195
201
|
for (const chunk of chunks) {
|
|
196
202
|
const prompt = buildHumanPersonScanPrompt({
|
|
197
203
|
persona_name: chunk.personaDisplayName,
|
|
198
204
|
messages_context: chunk.messages_context,
|
|
199
205
|
messages_analyze: chunk.messages_analyze,
|
|
206
|
+
participant_context: buildParticipantContext(context.personaId, state),
|
|
207
|
+
known_identifier_types: userIdentifierTypesForScan,
|
|
200
208
|
});
|
|
201
209
|
|
|
202
210
|
state.queue_enqueue({
|
|
@@ -364,88 +372,6 @@ export async function queueTopicMatch(
|
|
|
364
372
|
});
|
|
365
373
|
}
|
|
366
374
|
|
|
367
|
-
/**
|
|
368
|
-
* Queue a person match request using embedding-based similarity (people only).
|
|
369
|
-
*/
|
|
370
|
-
export async function queuePersonMatch(
|
|
371
|
-
candidate: PersonScanCandidate,
|
|
372
|
-
context: ExtractionContext,
|
|
373
|
-
state: StateManager,
|
|
374
|
-
extractionModel?: string
|
|
375
|
-
): Promise<void> {
|
|
376
|
-
const human = state.getHuman();
|
|
377
|
-
|
|
378
|
-
const peopleWithEmbeddings = human.people.filter(p => p.embedding && p.embedding.length > 0);
|
|
379
|
-
|
|
380
|
-
let topKItems: Array<{ id: string; name: string; description: string; relationship?: string }> = [];
|
|
381
|
-
|
|
382
|
-
if (peopleWithEmbeddings.length > 0) {
|
|
383
|
-
try {
|
|
384
|
-
const embeddingService = getEmbeddingService();
|
|
385
|
-
const candidateText = getPersonEmbeddingText({
|
|
386
|
-
name: candidate.name,
|
|
387
|
-
relationship: candidate.relationship,
|
|
388
|
-
description: candidate.description,
|
|
389
|
-
});
|
|
390
|
-
const candidateVector = await embeddingService.embed(candidateText);
|
|
391
|
-
|
|
392
|
-
const topK = findTopK(candidateVector, peopleWithEmbeddings, EMBEDDING_TOP_K);
|
|
393
|
-
topKItems = topK
|
|
394
|
-
.filter(({ similarity }) => similarity >= EMBEDDING_MIN_SIMILARITY)
|
|
395
|
-
.map(({ item }) => ({
|
|
396
|
-
id: item.id,
|
|
397
|
-
name: item.name,
|
|
398
|
-
description: item.description,
|
|
399
|
-
relationship: item.relationship,
|
|
400
|
-
}));
|
|
401
|
-
|
|
402
|
-
console.log(`[queuePersonMatch] Embedding search: ${peopleWithEmbeddings.length} people → ${topKItems.length} candidates`);
|
|
403
|
-
if (topKItems.length > 0) state.embedding_setWarning(false);
|
|
404
|
-
} catch (err) {
|
|
405
|
-
console.error(`[queuePersonMatch] Embedding search failed, falling back to recent people:`, err);
|
|
406
|
-
state.embedding_setWarning(true);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (topKItems.length === 0) {
|
|
411
|
-
const sorted = [...human.people].sort((a, b) => {
|
|
412
|
-
const aDate = a.last_mentioned ?? a.last_updated;
|
|
413
|
-
const bDate = b.last_mentioned ?? b.last_updated;
|
|
414
|
-
return bDate.localeCompare(aDate);
|
|
415
|
-
});
|
|
416
|
-
topKItems = sorted.slice(0, EMBEDDING_TOP_K).map(p => ({
|
|
417
|
-
id: p.id,
|
|
418
|
-
name: p.name,
|
|
419
|
-
description: p.description,
|
|
420
|
-
relationship: p.relationship,
|
|
421
|
-
}));
|
|
422
|
-
console.log(`[queuePersonMatch] No embedding matches, using ${topKItems.length} most-recent people`);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const prompt = buildPersonMatchPrompt({
|
|
426
|
-
candidate_name: candidate.name,
|
|
427
|
-
candidate_description: candidate.description,
|
|
428
|
-
candidate_relationship: candidate.relationship,
|
|
429
|
-
existing_people: topKItems,
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
state.queue_enqueue({
|
|
433
|
-
type: LLMRequestType.JSON,
|
|
434
|
-
priority: LLMPriority.Normal,
|
|
435
|
-
model: extractionModel,
|
|
436
|
-
system: prompt.system,
|
|
437
|
-
user: prompt.user,
|
|
438
|
-
next_step: LLMNextStep.HandlePersonMatch,
|
|
439
|
-
data: {
|
|
440
|
-
...context,
|
|
441
|
-
candidateName: candidate.name,
|
|
442
|
-
candidateDescription: candidate.description,
|
|
443
|
-
candidateRelationship: candidate.relationship,
|
|
444
|
-
extraction_model: extractionModel,
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
|
|
449
375
|
export function queueTopicUpdate(
|
|
450
376
|
matchResult: ItemMatchResult,
|
|
451
377
|
context: ExtractionContext & {
|
|
@@ -592,6 +518,7 @@ export function queuePersonUpdate(
|
|
|
592
518
|
candidateName: string;
|
|
593
519
|
candidateDescription: string;
|
|
594
520
|
candidateRelationship: string;
|
|
521
|
+
candidateIdentifiers?: PersonIdentifier[];
|
|
595
522
|
extraction_model?: string;
|
|
596
523
|
},
|
|
597
524
|
state: StateManager
|
|
@@ -605,11 +532,32 @@ export function queuePersonUpdate(
|
|
|
605
532
|
existingItem = human.people.find(p => p.id === matchedGuid) ?? null;
|
|
606
533
|
}
|
|
607
534
|
|
|
535
|
+
const candidateIdentifiers = context.candidateIdentifiers ?? [];
|
|
536
|
+
|
|
537
|
+
if (!isNewItem && existingItem && candidateIdentifiers.length > 0) {
|
|
538
|
+
const merged = [...(existingItem.identifiers ?? [])];
|
|
539
|
+
for (const ci of candidateIdentifiers) {
|
|
540
|
+
if (!merged.some(ei => ei.value === ci.value)) {
|
|
541
|
+
merged.push(ci);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
existingItem = { ...existingItem, identifiers: merged };
|
|
545
|
+
state.human_person_upsert(existingItem);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const userIdentifierTypes = [...new Set(
|
|
549
|
+
human.people
|
|
550
|
+
.flatMap(p => (p.identifiers ?? []).map(i => i.type))
|
|
551
|
+
.filter(Boolean)
|
|
552
|
+
)];
|
|
553
|
+
|
|
608
554
|
const extractionOptions: ExtractionOptions = { extraction_model: context.extraction_model };
|
|
609
555
|
const { chunks } = chunkExtractionContext(context, getExtractionMaxTokens(state, extractionOptions));
|
|
610
556
|
|
|
611
557
|
if (chunks.length === 0) return 0;
|
|
612
558
|
|
|
559
|
+
const primaryPersonaIdForUpdate = context.personaId.split("|")[0];
|
|
560
|
+
|
|
613
561
|
for (const chunk of chunks) {
|
|
614
562
|
const prompt = buildPersonUpdatePrompt({
|
|
615
563
|
existing_item: existingItem,
|
|
@@ -619,6 +567,8 @@ export function queuePersonUpdate(
|
|
|
619
567
|
messages_context: chunk.messages_context,
|
|
620
568
|
messages_analyze: chunk.messages_analyze,
|
|
621
569
|
persona_name: chunk.personaDisplayName,
|
|
570
|
+
participant_context: buildParticipantContext(primaryPersonaIdForUpdate, state),
|
|
571
|
+
known_identifier_types: userIdentifierTypes,
|
|
622
572
|
});
|
|
623
573
|
|
|
624
574
|
state.queue_enqueue({
|
|
@@ -634,7 +584,9 @@ export function queuePersonUpdate(
|
|
|
634
584
|
roomId: context.roomId,
|
|
635
585
|
isNewItem,
|
|
636
586
|
existingItemId: existingItem?.id,
|
|
587
|
+
candidateName: context.candidateName,
|
|
637
588
|
candidateRelationship: context.candidateRelationship,
|
|
589
|
+
candidateIdentifiers: isNewItem ? candidateIdentifiers : undefined,
|
|
638
590
|
analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
|
|
639
591
|
},
|
|
640
592
|
});
|
|
@@ -6,7 +6,6 @@ export {
|
|
|
6
6
|
queueAllScans,
|
|
7
7
|
queueTopicMatch,
|
|
8
8
|
queueTopicUpdate,
|
|
9
|
-
queuePersonMatch,
|
|
10
9
|
queuePersonUpdate,
|
|
11
10
|
queueEventSummary,
|
|
12
11
|
type ExtractionContext,
|
|
@@ -17,16 +16,13 @@ export {
|
|
|
17
16
|
startCeremony,
|
|
18
17
|
handleCeremonyProgress,
|
|
19
18
|
prunePersonaMessages,
|
|
20
|
-
queueExpirePhase,
|
|
21
|
-
queueExplorePhase,
|
|
22
|
-
queueDescriptionCheck,
|
|
23
19
|
runHumanCeremony,
|
|
24
20
|
} from "./ceremony.js";
|
|
25
|
-
export {
|
|
21
|
+
export { queueUserDedupRequest } from "./dedup-phase.js";
|
|
22
|
+
export { queuePersonMigration } from "./person-migration.js";
|
|
26
23
|
export {
|
|
27
|
-
|
|
28
|
-
queuePersonaTopicMatch,
|
|
29
|
-
queuePersonaTopicUpdate,
|
|
24
|
+
queuePersonaTopicRating,
|
|
30
25
|
type PersonaTopicContext,
|
|
26
|
+
type PersonaTopicOptions,
|
|
31
27
|
} from "./persona-topics.js";
|
|
32
28
|
export { queueRoomCapture, queuePersonaCapture, checkAndQueueRoomExtraction } from "./room-extraction.js";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LLMRequestType, LLMPriority, LLMNextStep } from "../types.js";
|
|
2
|
+
import type { StateManager } from "../state-manager.js";
|
|
3
|
+
import { buildPersonMigrationPrompt } from "../../prompts/ceremony/index.js";
|
|
4
|
+
|
|
5
|
+
export function queuePersonMigration(state: StateManager): void {
|
|
6
|
+
const human = state.getHuman();
|
|
7
|
+
|
|
8
|
+
if (human.settings?.people_migration_complete) {
|
|
9
|
+
console.log("[PersonMigration] Migration complete flag set — skipping");
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const unmigrated = human.people.filter(p => !p.identifiers || p.identifiers.length === 0);
|
|
14
|
+
|
|
15
|
+
if (unmigrated.length === 0) {
|
|
16
|
+
console.log("[PersonMigration] All Person records have identifiers — marking migration complete");
|
|
17
|
+
state.setHuman({
|
|
18
|
+
...human,
|
|
19
|
+
settings: {
|
|
20
|
+
...human.settings,
|
|
21
|
+
people_migration_complete: true,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(`[PersonMigration] Queuing migration for ${unmigrated.length} Person record(s)`);
|
|
28
|
+
|
|
29
|
+
const rewriteModel = human.settings?.rewrite_model;
|
|
30
|
+
|
|
31
|
+
for (const person of unmigrated) {
|
|
32
|
+
const prompt = buildPersonMigrationPrompt({
|
|
33
|
+
person: {
|
|
34
|
+
name: person.name,
|
|
35
|
+
description: person.description,
|
|
36
|
+
relationship: person.relationship,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
state.queue_enqueue({
|
|
41
|
+
type: LLMRequestType.JSON,
|
|
42
|
+
priority: LLMPriority.Normal,
|
|
43
|
+
system: prompt.system,
|
|
44
|
+
user: prompt.user,
|
|
45
|
+
next_step: LLMNextStep.HandlePersonIdentifierMigration,
|
|
46
|
+
...(rewriteModel ? { model: rewriteModel } : {}),
|
|
47
|
+
data: {
|
|
48
|
+
person_id: person.id,
|
|
49
|
+
ceremony_progress: 1,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`[PersonMigration] Queued ${unmigrated.length} migration request(s)`);
|
|
55
|
+
}
|
|
@@ -1,118 +1,93 @@
|
|
|
1
1
|
import { LLMRequestType, LLMPriority, LLMNextStep, type Message, type PersonaTopic } from "../types.js";
|
|
2
2
|
import type { StateManager } from "../state-manager.js";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
buildPersonaTopicMatchPrompt,
|
|
6
|
-
buildPersonaTopicUpdatePrompt,
|
|
7
|
-
type PersonaTopicScanCandidate,
|
|
8
|
-
type PersonaTopicMatchResult,
|
|
4
|
+
buildPersonaTopicRatingPrompt,
|
|
9
5
|
} from "../../prompts/persona/index.js";
|
|
6
|
+
import { chunkExtractionContext } from "./extraction-chunker.js";
|
|
7
|
+
import { resolveTokenLimit } from "../llm-client.js";
|
|
10
8
|
|
|
11
9
|
export interface PersonaTopicContext {
|
|
12
10
|
personaId: string;
|
|
13
11
|
personaDisplayName: string;
|
|
14
12
|
messages_context: Message[];
|
|
15
13
|
messages_analyze: Message[];
|
|
14
|
+
topics: PersonaTopic[];
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
export interface PersonaTopicOptions {
|
|
18
|
+
ceremony_progress?: number;
|
|
19
|
+
roomId?: string;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
persona_name: context.personaDisplayName,
|
|
26
|
-
messages_context: context.messages_context,
|
|
27
|
-
messages_analyze: context.messages_analyze,
|
|
28
|
-
});
|
|
22
|
+
const EXTRACTION_BUDGET_RATIO = 0.75;
|
|
23
|
+
const MIN_EXTRACTION_TOKENS = 10000;
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
next_step: LLMNextStep.HandlePersonaTopicScan,
|
|
36
|
-
data: {
|
|
37
|
-
personaId: context.personaId,
|
|
38
|
-
personaDisplayName: context.personaDisplayName,
|
|
39
|
-
analyze_from_timestamp: getAnalyzeFromTimestamp(context),
|
|
40
|
-
},
|
|
41
|
-
});
|
|
25
|
+
function getExtractionMaxTokens(state: StateManager): number {
|
|
26
|
+
const human = state.getHuman();
|
|
27
|
+
const modelForTokenLimit = human.settings?.default_model;
|
|
28
|
+
const tokenLimit = resolveTokenLimit(modelForTokenLimit, human.settings?.accounts);
|
|
29
|
+
return Math.max(MIN_EXTRACTION_TOKENS, Math.floor(tokenLimit * EXTRACTION_BUDGET_RATIO));
|
|
42
30
|
}
|
|
43
31
|
|
|
44
|
-
export function
|
|
45
|
-
candidate: PersonaTopicScanCandidate,
|
|
32
|
+
export function queuePersonaTopicRating(
|
|
46
33
|
context: PersonaTopicContext,
|
|
47
|
-
state: StateManager
|
|
34
|
+
state: StateManager,
|
|
35
|
+
options?: PersonaTopicOptions
|
|
48
36
|
): void {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const prompt = buildPersonaTopicMatchPrompt({
|
|
56
|
-
persona_name: context.personaDisplayName,
|
|
57
|
-
candidate,
|
|
58
|
-
existing_topics: persona.topics,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
state.queue_enqueue({
|
|
62
|
-
type: LLMRequestType.JSON,
|
|
63
|
-
priority: LLMPriority.Low,
|
|
64
|
-
system: prompt.system,
|
|
65
|
-
user: prompt.user,
|
|
66
|
-
next_step: LLMNextStep.HandlePersonaTopicMatch,
|
|
67
|
-
data: {
|
|
37
|
+
const maxTokens = getExtractionMaxTokens(state);
|
|
38
|
+
const { chunks } = chunkExtractionContext(
|
|
39
|
+
{
|
|
68
40
|
personaId: context.personaId,
|
|
69
41
|
personaDisplayName: context.personaDisplayName,
|
|
70
|
-
|
|
71
|
-
|
|
42
|
+
messages_context: context.messages_context,
|
|
43
|
+
messages_analyze: context.messages_analyze,
|
|
72
44
|
},
|
|
73
|
-
|
|
74
|
-
|
|
45
|
+
maxTokens
|
|
46
|
+
);
|
|
75
47
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
matchResult: PersonaTopicMatchResult,
|
|
79
|
-
context: PersonaTopicContext,
|
|
80
|
-
state: StateManager
|
|
81
|
-
): void {
|
|
82
|
-
const persona = state.persona_getById(context.personaId);
|
|
83
|
-
if (!persona) {
|
|
84
|
-
console.error(`[queuePersonaTopicUpdate] Persona not found: ${context.personaId}`);
|
|
48
|
+
if (chunks.length === 0) {
|
|
49
|
+
console.log(`[queuePersonaTopicRating] No chunks to process for ${context.personaDisplayName}`);
|
|
85
50
|
return;
|
|
86
51
|
}
|
|
87
52
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
53
|
+
// Mark messages BEFORE queueing to prevent duplicate queueing
|
|
54
|
+
const shortId = context.personaId.slice(0, 8);
|
|
55
|
+
const allAnalyzeIds = context.messages_analyze.map(m => m.id);
|
|
56
|
+
if (options?.roomId) {
|
|
57
|
+
state.markRoomMessagesPersonaExtracted(options.roomId, allAnalyzeIds, shortId);
|
|
58
|
+
} else {
|
|
59
|
+
state.messages_markPersonaExtracted(context.personaId, allAnalyzeIds, shortId);
|
|
60
|
+
}
|
|
91
61
|
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
candidate,
|
|
99
|
-
messages_context: context.messages_context,
|
|
100
|
-
messages_analyze: context.messages_analyze,
|
|
101
|
-
});
|
|
62
|
+
for (const chunk of chunks) {
|
|
63
|
+
const topicsForPrompt = context.topics.map(t => ({
|
|
64
|
+
id: t.id,
|
|
65
|
+
name: t.name,
|
|
66
|
+
description_hint: t.perspective?.slice(0, 80) || t.name,
|
|
67
|
+
}));
|
|
102
68
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
69
|
+
const prompt = buildPersonaTopicRatingPrompt({
|
|
70
|
+
persona_name: context.personaDisplayName,
|
|
71
|
+
topics: topicsForPrompt,
|
|
72
|
+
messages_context: chunk.messages_context,
|
|
73
|
+
messages_analyze: chunk.messages_analyze,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
state.queue_enqueue({
|
|
77
|
+
type: LLMRequestType.JSON,
|
|
78
|
+
priority: LLMPriority.Low,
|
|
79
|
+
system: prompt.system,
|
|
80
|
+
user: prompt.user,
|
|
81
|
+
next_step: LLMNextStep.HandlePersonaTopicRating,
|
|
82
|
+
data: {
|
|
83
|
+
personaId: context.personaId,
|
|
84
|
+
personaDisplayName: context.personaDisplayName,
|
|
85
|
+
message_ids: chunk.messages_analyze.map(m => m.id),
|
|
86
|
+
ceremony_progress: options?.ceremony_progress,
|
|
87
|
+
roomId: options?.roomId,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`[queuePersonaTopicRating] Queued ${chunks.length} rating chunk(s) for ${context.personaDisplayName}`);
|
|
118
93
|
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
queueEventSummary,
|
|
18
18
|
type ExtractionContext as HumanExtractionContext,
|
|
19
19
|
} from "./human-extraction.js";
|
|
20
|
+
import { queuePersonaTopicRating, type PersonaTopicContext } from "./persona-topics.js";
|
|
20
21
|
|
|
21
22
|
const EXTRACTION_BUDGET_RATIO = 0.75;
|
|
22
23
|
const MIN_EXTRACTION_TOKENS = 10000;
|
|
@@ -269,6 +270,24 @@ export function queueRoomCapture(state: StateManager, roomId: string): void {
|
|
|
269
270
|
}
|
|
270
271
|
queueRoomEventScan(roomId, roomDisplayName, allVisible, state, participantContext);
|
|
271
272
|
|
|
273
|
+
for (const personaId of room.persona_ids) {
|
|
274
|
+
const shortId = personaId.slice(0, 8);
|
|
275
|
+
const unprocessedRaw = state.getRoomUnextractedMessagesForPersona(roomId, shortId);
|
|
276
|
+
if (unprocessedRaw.length === 0) continue;
|
|
277
|
+
const personaForRoom = state.persona_getById(personaId);
|
|
278
|
+
if (!personaForRoom) continue;
|
|
279
|
+
const processedIds = new Set(allVisible.filter(m => !!m.persona_extracted?.[shortId]).map(m => m.id));
|
|
280
|
+
const personaTopicContext: PersonaTopicContext = {
|
|
281
|
+
personaId,
|
|
282
|
+
personaDisplayName: personaForRoom.display_name,
|
|
283
|
+
messages_context: allVisible.filter(m => processedIds.has(m.id)),
|
|
284
|
+
messages_analyze: normalizeRoomMessages(unprocessedRaw, state),
|
|
285
|
+
topics: personaForRoom.topics,
|
|
286
|
+
};
|
|
287
|
+
queuePersonaTopicRating(personaTopicContext, state, { roomId: roomId });
|
|
288
|
+
console.log(`[queueRoomCapture] Queued persona topic scan: ${personaForRoom.display_name} (${unprocessedRaw.length} messages)`);
|
|
289
|
+
}
|
|
290
|
+
|
|
272
291
|
console.log(`[queueRoomCapture] Queued extraction for room ${roomDisplayName}`);
|
|
273
292
|
}
|
|
274
293
|
|
|
@@ -314,5 +333,20 @@ export function queuePersonaCapture(state: StateManager, personaId: string): voi
|
|
|
314
333
|
|
|
315
334
|
queueEventSummary(personaId, state, options);
|
|
316
335
|
|
|
336
|
+
const shortId = personaId.slice(0, 8);
|
|
337
|
+
const unprocessedForPersona = state.messages_getUnextractedForPersona(personaId, shortId);
|
|
338
|
+
if (unprocessedForPersona.length > 0) {
|
|
339
|
+
const processedIds = new Set(allMessages.filter(m => !!m.persona_extracted?.[shortId]).map(m => m.id));
|
|
340
|
+
const personaTopicContext: PersonaTopicContext = {
|
|
341
|
+
personaId,
|
|
342
|
+
personaDisplayName: persona.display_name,
|
|
343
|
+
messages_context: allMessages.filter(m => processedIds.has(m.id)),
|
|
344
|
+
messages_analyze: unprocessedForPersona,
|
|
345
|
+
topics: persona.topics,
|
|
346
|
+
};
|
|
347
|
+
queuePersonaTopicRating(personaTopicContext, state);
|
|
348
|
+
console.log(`[queuePersonaCapture] Queued persona topic scan for ${persona.display_name} (${unprocessedForPersona.length} messages)`);
|
|
349
|
+
}
|
|
350
|
+
|
|
317
351
|
console.log(`[queuePersonaCapture] Queued extraction for persona ${persona.display_name}`);
|
|
318
352
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "./types.js";
|
|
8
8
|
import { StateManager } from "./state-manager.js";
|
|
9
9
|
import { orchestratePersonaGeneration } from "./orchestrators/index.js";
|
|
10
|
+
import { computePersonaDescriptionEmbedding } from "./embedding-service.js";
|
|
10
11
|
|
|
11
12
|
export async function getPersonaList(sm: StateManager): Promise<PersonaSummary[]> {
|
|
12
13
|
return sm.persona_getAll().map((entity) => ({
|
|
@@ -41,6 +42,9 @@ export async function createPersona(
|
|
|
41
42
|
`Cannot create persona with reserved name "${input.name}". Reserved names: ${RESERVED_PERSONA_NAMES.join(", ")}`
|
|
42
43
|
);
|
|
43
44
|
}
|
|
45
|
+
if (!input.long_description?.trim()) {
|
|
46
|
+
throw new Error(`Persona "${input.name}" requires a long description.`);
|
|
47
|
+
}
|
|
44
48
|
const now = new Date().toISOString();
|
|
45
49
|
const DEFAULT_GROUP = "General";
|
|
46
50
|
const personaId = crypto.randomUUID();
|
|
@@ -117,16 +121,31 @@ export async function updatePersona(
|
|
|
117
121
|
): Promise<boolean> {
|
|
118
122
|
const persona = sm.persona_getById(personaId);
|
|
119
123
|
if (!persona) return false;
|
|
124
|
+
|
|
125
|
+
if ('long_description' in updates) {
|
|
126
|
+
const merged = { ...persona, ...updates };
|
|
127
|
+
const embedding = await computePersonaDescriptionEmbedding(merged);
|
|
128
|
+
if (embedding) {
|
|
129
|
+
updates = { ...updates, description_embedding: embedding };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
120
133
|
sm.persona_update(personaId, updates);
|
|
121
134
|
return true;
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
export async function getGroupList(sm: StateManager): Promise<string[]> {
|
|
125
|
-
const personas = sm.persona_getAll();
|
|
126
138
|
const groups = new Set<string>();
|
|
127
|
-
|
|
139
|
+
|
|
140
|
+
for (const p of sm.persona_getAll()) {
|
|
128
141
|
if (p.group_primary) groups.add(p.group_primary);
|
|
129
142
|
for (const g of p.groups_visible || []) groups.add(g);
|
|
130
143
|
}
|
|
144
|
+
|
|
145
|
+
const human = sm.getHuman();
|
|
146
|
+
for (const item of [...(human.topics || []), ...(human.people || []), ...(human.facts || [])]) {
|
|
147
|
+
for (const g of item.persona_groups || []) groups.add(g);
|
|
148
|
+
}
|
|
149
|
+
|
|
131
150
|
return [...groups].sort();
|
|
132
151
|
}
|