ei-tui 0.4.2 → 0.5.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 +14 -0
- package/package.json +1 -1
- package/src/cli/README.md +21 -14
- package/src/cli/commands/personas.ts +12 -0
- package/src/cli/mcp.ts +6 -5
- package/src/cli/retrieval.ts +86 -8
- package/src/cli.ts +21 -19
- package/src/core/constants/seed-traits.ts +29 -0
- package/src/core/context-utils.ts +1 -0
- package/src/core/format-utils.ts +23 -0
- package/src/core/handlers/human-matching.ts +53 -35
- package/src/core/handlers/index.ts +5 -0
- package/src/core/handlers/persona-preview.ts +7 -0
- package/src/core/handlers/persona-topics.ts +3 -2
- package/src/core/handlers/rooms.ts +176 -0
- package/src/core/handlers/utils.ts +55 -3
- package/src/core/heartbeat-manager.ts +3 -1
- package/src/core/llm-client.ts +1 -1
- package/src/core/message-manager.ts +13 -9
- package/src/core/orchestrators/human-extraction.ts +15 -2
- package/src/core/orchestrators/index.ts +1 -0
- package/src/core/orchestrators/persona-generation.ts +4 -0
- package/src/core/orchestrators/persona-topics.ts +2 -1
- package/src/core/orchestrators/room-extraction.ts +318 -0
- package/src/core/persona-manager.ts +16 -5
- package/src/core/personas/opencode-agent.ts +12 -2
- package/src/core/processor.ts +520 -4
- package/src/core/prompt-context-builder.ts +89 -5
- package/src/core/queue-processor.ts +68 -8
- package/src/core/room-manager.ts +408 -0
- package/src/core/state/index.ts +1 -0
- package/src/core/state/personas.ts +12 -2
- package/src/core/state/queue.ts +2 -2
- package/src/core/state/rooms.ts +182 -0
- package/src/core/state-manager.ts +124 -2
- package/src/core/tool-manager.ts +1 -1
- package/src/core/tools/index.ts +15 -0
- package/src/core/types/entities.ts +1 -0
- package/src/core/types/enums.ts +11 -0
- package/src/core/types/integrations.ts +10 -2
- package/src/core/types/llm.ts +3 -0
- package/src/core/types/rooms.ts +59 -0
- package/src/core/types.ts +1 -0
- package/src/core/utils/decay.ts +14 -8
- package/src/core/utils/exposure.ts +14 -0
- package/src/integrations/claude-code/importer.ts +23 -10
- package/src/integrations/cursor/importer.ts +22 -10
- package/src/integrations/opencode/importer.ts +30 -13
- package/src/prompts/ceremony/dedup.ts +2 -2
- package/src/prompts/generation/from-person.ts +85 -0
- package/src/prompts/generation/index.ts +2 -0
- package/src/prompts/generation/persona.ts +14 -10
- package/src/prompts/generation/seeds.ts +4 -29
- package/src/prompts/generation/types.ts +13 -0
- package/src/prompts/heartbeat/check.ts +1 -1
- package/src/prompts/heartbeat/ei.ts +4 -4
- package/src/prompts/heartbeat/types.ts +1 -0
- package/src/prompts/index.ts +15 -0
- package/src/prompts/message-utils.ts +2 -2
- package/src/prompts/persona/topics-match.ts +7 -6
- package/src/prompts/persona/topics-update.ts +8 -11
- package/src/prompts/persona/types.ts +2 -1
- package/src/prompts/response/index.ts +4 -11
- package/src/prompts/response/sections.ts +22 -10
- package/src/prompts/response/types.ts +6 -0
- package/src/prompts/room/index.ts +115 -0
- package/src/prompts/room/sections.ts +150 -0
- package/src/prompts/room/types.ts +93 -0
- package/tui/README.md +20 -0
- package/tui/src/app.tsx +3 -2
- package/tui/src/commands/activate.tsx +98 -0
- package/tui/src/commands/archive.tsx +54 -25
- package/tui/src/commands/capture.tsx +50 -0
- package/tui/src/commands/dedupe.tsx +2 -7
- package/tui/src/commands/delete.tsx +48 -0
- package/tui/src/commands/details.tsx +7 -0
- package/tui/src/commands/persona.tsx +271 -9
- package/tui/src/commands/room.tsx +261 -0
- package/tui/src/commands/silence.tsx +29 -0
- package/tui/src/components/ArchivedItemsOverlay.tsx +144 -0
- package/tui/src/components/ConfirmOverlay.tsx +6 -0
- package/tui/src/components/ConflictOverlay.tsx +6 -0
- package/tui/src/components/HelpOverlay.tsx +6 -1
- package/tui/src/components/LoadingOverlay.tsx +51 -0
- package/tui/src/components/MessageList.tsx +1 -18
- package/tui/src/components/PersonPickerOverlay.tsx +121 -0
- package/tui/src/components/PersonaListOverlay.tsx +6 -1
- package/tui/src/components/PromptInput.tsx +141 -8
- package/tui/src/components/ProviderListOverlay.tsx +5 -1
- package/tui/src/components/QuotesOverlay.tsx +5 -1
- package/tui/src/components/RoomMessageList.tsx +179 -0
- package/tui/src/components/Sidebar.tsx +54 -2
- package/tui/src/components/StatusBar.tsx +99 -8
- package/tui/src/components/ToolkitListOverlay.tsx +5 -1
- package/tui/src/components/WelcomeOverlay.tsx +6 -0
- package/tui/src/context/ei.tsx +252 -1
- package/tui/src/context/keyboard.tsx +48 -12
- package/tui/src/util/cyp-editor.tsx +152 -0
- package/tui/src/util/quote-utils.ts +19 -0
- package/tui/src/util/room-editor.tsx +164 -0
- package/tui/src/util/room-logic.ts +8 -0
- package/tui/src/util/room-parser.ts +70 -0
- package/tui/src/util/yaml-serializers.ts +154 -0
|
@@ -58,6 +58,7 @@ export interface ExtractionContext {
|
|
|
58
58
|
messages_context: Message[];
|
|
59
59
|
messages_analyze: Message[];
|
|
60
60
|
extraction_flag?: "f" | "t" | "p" | "e";
|
|
61
|
+
roomId?: string;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
export interface ExtractionOptions {
|
|
@@ -67,6 +68,15 @@ export interface ExtractionOptions {
|
|
|
67
68
|
extraction_model?: string;
|
|
68
69
|
/** Override token budget for chunking */
|
|
69
70
|
extraction_token_limit?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Controls whether external (integration-imported) messages are included.
|
|
73
|
+
* - "exclude": skip messages where external === true
|
|
74
|
+
* - "only": include ONLY messages where external === true
|
|
75
|
+
* - "include": include all messages (backward-compat default; omit means same)
|
|
76
|
+
*
|
|
77
|
+
* NOTE: "include" is the backward-compat default only. All new callers must explicitly pass "exclude" or "only". Will be removed in a future release.
|
|
78
|
+
*/
|
|
79
|
+
external_filter?: "include" | "exclude" | "only";
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
function getAnalyzeFromTimestamp(context: ExtractionContext): string | null {
|
|
@@ -466,6 +476,7 @@ export function queueTopicUpdate(
|
|
|
466
476
|
if (chunks.length === 0) return 0;
|
|
467
477
|
|
|
468
478
|
for (const chunk of chunks) {
|
|
479
|
+
const primaryPersonaId = context.personaId.split("|")[0];
|
|
469
480
|
const prompt = buildTopicUpdatePrompt({
|
|
470
481
|
existing_item: existingItem,
|
|
471
482
|
new_topic_name: isNewItem ? context.candidateName : undefined,
|
|
@@ -474,7 +485,7 @@ export function queueTopicUpdate(
|
|
|
474
485
|
messages_context: chunk.messages_context,
|
|
475
486
|
messages_analyze: chunk.messages_analyze,
|
|
476
487
|
persona_name: chunk.personaDisplayName,
|
|
477
|
-
participant_context: buildParticipantContext(
|
|
488
|
+
participant_context: buildParticipantContext(primaryPersonaId, state),
|
|
478
489
|
});
|
|
479
490
|
|
|
480
491
|
state.queue_enqueue({
|
|
@@ -487,6 +498,7 @@ export function queueTopicUpdate(
|
|
|
487
498
|
data: {
|
|
488
499
|
personaId: context.personaId,
|
|
489
500
|
personaDisplayName: context.personaDisplayName,
|
|
501
|
+
roomId: context.roomId,
|
|
490
502
|
isNewItem,
|
|
491
503
|
existingItemId: existingItem?.id,
|
|
492
504
|
candidateCategory: context.candidateCategory,
|
|
@@ -509,7 +521,7 @@ export function queueEventSummary(
|
|
|
509
521
|
return 0;
|
|
510
522
|
}
|
|
511
523
|
|
|
512
|
-
const unextractedMessages = state.messages_getUnextracted(personaId, "e");
|
|
524
|
+
const unextractedMessages = state.messages_getUnextracted(personaId, "e", undefined, options?.external_filter);
|
|
513
525
|
if (unextractedMessages.length === 0) {
|
|
514
526
|
console.log(`[queueEventSummary] No unprocessed messages for ${persona.display_name}`);
|
|
515
527
|
return 0;
|
|
@@ -624,6 +636,7 @@ export function queuePersonUpdate(
|
|
|
624
636
|
data: {
|
|
625
637
|
personaId: context.personaId,
|
|
626
638
|
personaDisplayName: context.personaDisplayName,
|
|
639
|
+
roomId: context.roomId,
|
|
627
640
|
isNewItem,
|
|
628
641
|
existingItemId: existingItem?.id,
|
|
629
642
|
candidateRelationship: context.candidateRelationship,
|
|
@@ -39,12 +39,16 @@ export function orchestratePersonaGeneration(
|
|
|
39
39
|
const needsMoreTopics = topicCount < 3;
|
|
40
40
|
|
|
41
41
|
if (needsShortDescription || needsMoreTraits || needsMoreTopics) {
|
|
42
|
+
const filteredTraits = (partial.traits ?? []).filter(t => t.name?.trim());
|
|
43
|
+
const filteredTopics = (partial.topics ?? []).filter(t => t.name?.trim());
|
|
42
44
|
const prompt = buildPersonaGenerationPrompt({
|
|
43
45
|
name: partial.name,
|
|
44
46
|
long_description: partial.long_description,
|
|
45
47
|
short_description: partial.short_description,
|
|
46
48
|
existing_traits: partial.traits,
|
|
47
49
|
existing_topics: partial.topics,
|
|
50
|
+
filtered_traits: filteredTraits,
|
|
51
|
+
filtered_topics: filteredTopics,
|
|
48
52
|
});
|
|
49
53
|
|
|
50
54
|
stateManager.queue_enqueue({
|
|
@@ -110,7 +110,8 @@ export function queuePersonaTopicUpdate(
|
|
|
110
110
|
personaId: context.personaId,
|
|
111
111
|
personaDisplayName: context.personaDisplayName,
|
|
112
112
|
candidate,
|
|
113
|
-
|
|
113
|
+
existingTopicId: existingTopic?.id ?? null,
|
|
114
|
+
isNewTopic: !existingTopic,
|
|
114
115
|
analyze_from_timestamp: getAnalyzeFromTimestamp(context),
|
|
115
116
|
},
|
|
116
117
|
});
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { LLMNextStep, LLMPriority, LLMRequestType, RoomMode } from "../types.js";
|
|
2
|
+
import type { Message } from "../types.js";
|
|
3
|
+
import type { StateManager } from "../state-manager.js";
|
|
4
|
+
import {
|
|
5
|
+
buildHumanTopicScanPrompt,
|
|
6
|
+
buildHumanPersonScanPrompt,
|
|
7
|
+
buildEventScanPrompt,
|
|
8
|
+
type ParticipantContext,
|
|
9
|
+
} from "../../prompts/human/index.js";
|
|
10
|
+
import { chunkExtractionContext } from "./extraction-chunker.js";
|
|
11
|
+
import { buildEventWindows } from "../utils/event-windows.js";
|
|
12
|
+
import { resolveTokenLimit } from "../llm-client.js";
|
|
13
|
+
import { normalizeRoomMessages } from "../handlers/utils.js";
|
|
14
|
+
import {
|
|
15
|
+
queueTopicScan,
|
|
16
|
+
queuePersonScan,
|
|
17
|
+
queueEventSummary,
|
|
18
|
+
type ExtractionContext as HumanExtractionContext,
|
|
19
|
+
} from "./human-extraction.js";
|
|
20
|
+
|
|
21
|
+
const EXTRACTION_BUDGET_RATIO = 0.75;
|
|
22
|
+
const MIN_EXTRACTION_TOKENS = 10000;
|
|
23
|
+
|
|
24
|
+
function getExtractionMaxTokens(state: StateManager): number {
|
|
25
|
+
const human = state.getHuman();
|
|
26
|
+
const tokenLimit = resolveTokenLimit(human.settings?.default_model, human.settings?.accounts);
|
|
27
|
+
return Math.max(MIN_EXTRACTION_TOKENS, Math.floor(tokenLimit * EXTRACTION_BUDGET_RATIO));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildRoomParticipantContext(roomId: string, state: StateManager): ParticipantContext {
|
|
31
|
+
const room = state.getRoom(roomId);
|
|
32
|
+
const human = state.getHuman();
|
|
33
|
+
const humanName = human.settings?.name_display;
|
|
34
|
+
|
|
35
|
+
const fullNameFact = human.facts.find(f => f.name === "Full Name");
|
|
36
|
+
const nicknameFact = human.facts.find(f => f.name === "Nickname/Preferred Name");
|
|
37
|
+
const fullName = fullNameFact?.description || "";
|
|
38
|
+
const nickname = nicknameFact?.description || "";
|
|
39
|
+
let human_name: string | undefined;
|
|
40
|
+
if (fullName && nickname) human_name = `${fullName} (${nickname})`;
|
|
41
|
+
else if (fullName) human_name = fullName;
|
|
42
|
+
else if (nickname) human_name = nickname;
|
|
43
|
+
else if (humanName) human_name = humanName;
|
|
44
|
+
|
|
45
|
+
const participantNames = (room?.persona_ids ?? [])
|
|
46
|
+
.map(id => state.persona_getById(id)?.display_name)
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join(", ");
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
persona_name: room?.display_name ?? roomId,
|
|
52
|
+
persona_description: participantNames
|
|
53
|
+
? `A conversation room with participants: ${participantNames}`
|
|
54
|
+
: "A multi-persona conversation room",
|
|
55
|
+
human_name,
|
|
56
|
+
human_age: undefined,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getRoomVisibleMessages(state: StateManager, roomId: string): Message[] {
|
|
61
|
+
const room = state.getRoom(roomId);
|
|
62
|
+
if (!room) return [];
|
|
63
|
+
const rawMessages = room.mode === RoomMode.FreeForAll
|
|
64
|
+
? [...state.getRoomMessages(roomId)].sort((a, b) => a.timestamp.localeCompare(b.timestamp))
|
|
65
|
+
: state.getRoomActivePath(roomId);
|
|
66
|
+
return normalizeRoomMessages(rawMessages, state);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function queueRoomTopicScan(
|
|
70
|
+
roomId: string,
|
|
71
|
+
roomDisplayName: string,
|
|
72
|
+
messages_context: Message[],
|
|
73
|
+
messages_analyze: Message[],
|
|
74
|
+
state: StateManager,
|
|
75
|
+
participantContext: ParticipantContext
|
|
76
|
+
): void {
|
|
77
|
+
const context: HumanExtractionContext = {
|
|
78
|
+
personaId: roomId,
|
|
79
|
+
personaDisplayName: roomDisplayName,
|
|
80
|
+
messages_context,
|
|
81
|
+
messages_analyze,
|
|
82
|
+
extraction_flag: "t",
|
|
83
|
+
};
|
|
84
|
+
const { chunks } = chunkExtractionContext(context, getExtractionMaxTokens(state));
|
|
85
|
+
if (chunks.length === 0) return;
|
|
86
|
+
|
|
87
|
+
state.markRoomMessagesExtracted(roomId, messages_analyze.map(m => m.id), "t");
|
|
88
|
+
|
|
89
|
+
for (const chunk of chunks) {
|
|
90
|
+
const prompt = buildHumanTopicScanPrompt({
|
|
91
|
+
persona_name: roomDisplayName,
|
|
92
|
+
messages_context: chunk.messages_context,
|
|
93
|
+
messages_analyze: chunk.messages_analyze,
|
|
94
|
+
participant_context: participantContext,
|
|
95
|
+
});
|
|
96
|
+
state.queue_enqueue({
|
|
97
|
+
type: LLMRequestType.JSON,
|
|
98
|
+
priority: LLMPriority.Low,
|
|
99
|
+
system: prompt.system,
|
|
100
|
+
user: prompt.user,
|
|
101
|
+
next_step: LLMNextStep.HandleHumanTopicScan,
|
|
102
|
+
data: {
|
|
103
|
+
roomId,
|
|
104
|
+
personaId: (state.getRoom(roomId)?.persona_ids ?? []).join("|"),
|
|
105
|
+
personaDisplayName: roomDisplayName,
|
|
106
|
+
message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function queueRoomPersonScan(
|
|
113
|
+
roomId: string,
|
|
114
|
+
roomDisplayName: string,
|
|
115
|
+
messages_context: Message[],
|
|
116
|
+
messages_analyze: Message[],
|
|
117
|
+
state: StateManager
|
|
118
|
+
): void {
|
|
119
|
+
const context: HumanExtractionContext = {
|
|
120
|
+
personaId: roomId,
|
|
121
|
+
personaDisplayName: roomDisplayName,
|
|
122
|
+
messages_context,
|
|
123
|
+
messages_analyze,
|
|
124
|
+
extraction_flag: "p",
|
|
125
|
+
};
|
|
126
|
+
const { chunks } = chunkExtractionContext(context, getExtractionMaxTokens(state));
|
|
127
|
+
if (chunks.length === 0) return;
|
|
128
|
+
|
|
129
|
+
state.markRoomMessagesExtracted(roomId, messages_analyze.map(m => m.id), "p");
|
|
130
|
+
|
|
131
|
+
for (const chunk of chunks) {
|
|
132
|
+
const prompt = buildHumanPersonScanPrompt({
|
|
133
|
+
persona_name: roomDisplayName,
|
|
134
|
+
messages_context: chunk.messages_context,
|
|
135
|
+
messages_analyze: chunk.messages_analyze,
|
|
136
|
+
});
|
|
137
|
+
state.queue_enqueue({
|
|
138
|
+
type: LLMRequestType.JSON,
|
|
139
|
+
priority: LLMPriority.Low,
|
|
140
|
+
system: prompt.system,
|
|
141
|
+
user: prompt.user,
|
|
142
|
+
next_step: LLMNextStep.HandleHumanPersonScan,
|
|
143
|
+
data: {
|
|
144
|
+
roomId,
|
|
145
|
+
personaId: (state.getRoom(roomId)?.persona_ids ?? []).join("|"),
|
|
146
|
+
personaDisplayName: roomDisplayName,
|
|
147
|
+
message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function queueRoomEventScan(
|
|
154
|
+
roomId: string,
|
|
155
|
+
roomDisplayName: string,
|
|
156
|
+
allMessages: Message[],
|
|
157
|
+
state: StateManager,
|
|
158
|
+
participantContext: ParticipantContext
|
|
159
|
+
): void {
|
|
160
|
+
const unextracted = allMessages.filter(m => !m.e);
|
|
161
|
+
if (unextracted.length === 0) return;
|
|
162
|
+
|
|
163
|
+
const human = state.getHuman();
|
|
164
|
+
const gapHours = human.settings?.ceremony?.event_window_hours ?? 8;
|
|
165
|
+
const sorted = [...unextracted].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
166
|
+
const windows = buildEventWindows(sorted, gapHours);
|
|
167
|
+
|
|
168
|
+
state.markRoomMessagesExtracted(roomId, sorted.map(m => m.id), "e");
|
|
169
|
+
|
|
170
|
+
for (const windowMessages of windows) {
|
|
171
|
+
if (windowMessages.length === 0) continue;
|
|
172
|
+
const windowStartTime = new Date(windowMessages[0].timestamp).getTime();
|
|
173
|
+
const messages_context = allMessages.filter(
|
|
174
|
+
m => m.e === true && new Date(m.timestamp).getTime() < windowStartTime
|
|
175
|
+
);
|
|
176
|
+
const context: HumanExtractionContext = {
|
|
177
|
+
personaId: roomId,
|
|
178
|
+
personaDisplayName: roomDisplayName,
|
|
179
|
+
messages_context,
|
|
180
|
+
messages_analyze: windowMessages,
|
|
181
|
+
extraction_flag: "e",
|
|
182
|
+
};
|
|
183
|
+
const { chunks } = chunkExtractionContext(context, getExtractionMaxTokens(state));
|
|
184
|
+
for (const chunk of chunks) {
|
|
185
|
+
const prompt = buildEventScanPrompt({
|
|
186
|
+
persona_name: roomDisplayName,
|
|
187
|
+
messages_context: chunk.messages_context,
|
|
188
|
+
messages_analyze: chunk.messages_analyze,
|
|
189
|
+
participant_context: participantContext,
|
|
190
|
+
});
|
|
191
|
+
state.queue_enqueue({
|
|
192
|
+
type: LLMRequestType.JSON,
|
|
193
|
+
priority: LLMPriority.Low,
|
|
194
|
+
system: prompt.system,
|
|
195
|
+
user: prompt.user,
|
|
196
|
+
next_step: LLMNextStep.HandleEventScan,
|
|
197
|
+
data: {
|
|
198
|
+
roomId,
|
|
199
|
+
personaId: (state.getRoom(roomId)?.persona_ids ?? []).join("|"),
|
|
200
|
+
personaDisplayName: roomDisplayName,
|
|
201
|
+
message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function checkAndQueueRoomExtraction(state: StateManager, roomId: string): void {
|
|
209
|
+
const room = state.getRoom(roomId);
|
|
210
|
+
if (!room || room.mode === RoomMode.ChooseYourPath) return;
|
|
211
|
+
|
|
212
|
+
const N = room.persona_ids.length + 1;
|
|
213
|
+
const threshold = room.mode === RoomMode.FreeForAll ? 5 * N : 10;
|
|
214
|
+
|
|
215
|
+
const allVisible = getRoomVisibleMessages(state, roomId);
|
|
216
|
+
const unextractedT = allVisible.filter(m => !m.t);
|
|
217
|
+
const unextractedP = allVisible.filter(m => !m.p);
|
|
218
|
+
|
|
219
|
+
if (unextractedT.length < threshold && unextractedP.length < threshold) return;
|
|
220
|
+
|
|
221
|
+
const participantContext = buildRoomParticipantContext(roomId, state);
|
|
222
|
+
const roomDisplayName = room.display_name;
|
|
223
|
+
|
|
224
|
+
if (unextractedT.length >= threshold) {
|
|
225
|
+
const analyzeStart = unextractedT[0].timestamp;
|
|
226
|
+
const messages_contextT = allVisible.filter(
|
|
227
|
+
m => m.t === true && new Date(m.timestamp).getTime() < new Date(analyzeStart).getTime()
|
|
228
|
+
);
|
|
229
|
+
queueRoomTopicScan(roomId, roomDisplayName, messages_contextT, unextractedT, state, participantContext);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (unextractedP.length >= threshold) {
|
|
233
|
+
const analyzeStart = unextractedP[0].timestamp;
|
|
234
|
+
const messages_contextP = allVisible.filter(
|
|
235
|
+
m => m.p === true && new Date(m.timestamp).getTime() < new Date(analyzeStart).getTime()
|
|
236
|
+
);
|
|
237
|
+
queueRoomPersonScan(roomId, roomDisplayName, messages_contextP, unextractedP, state);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log(`[checkAndQueueRoomExtraction] Auto-triggered extraction for room ${roomDisplayName} (threshold: ${threshold})`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function queueRoomCapture(state: StateManager, roomId: string): void {
|
|
244
|
+
const room = state.getRoom(roomId);
|
|
245
|
+
if (!room) return;
|
|
246
|
+
|
|
247
|
+
const allVisible = getRoomVisibleMessages(state, roomId);
|
|
248
|
+
if (allVisible.length === 0) return;
|
|
249
|
+
|
|
250
|
+
const participantContext = buildRoomParticipantContext(roomId, state);
|
|
251
|
+
const roomDisplayName = room.display_name;
|
|
252
|
+
|
|
253
|
+
const unextractedT = allVisible.filter(m => !m.t);
|
|
254
|
+
const unextractedP = allVisible.filter(m => !m.p);
|
|
255
|
+
const analyzeStartT = unextractedT[0]?.timestamp ?? "9999";
|
|
256
|
+
const messages_contextT = allVisible.filter(
|
|
257
|
+
m => m.t === true && new Date(m.timestamp).getTime() < new Date(analyzeStartT).getTime()
|
|
258
|
+
);
|
|
259
|
+
const analyzeStartP = unextractedP[0]?.timestamp ?? "9999";
|
|
260
|
+
const messages_contextP = allVisible.filter(
|
|
261
|
+
m => m.p === true && new Date(m.timestamp).getTime() < new Date(analyzeStartP).getTime()
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (unextractedT.length > 0) {
|
|
265
|
+
queueRoomTopicScan(roomId, roomDisplayName, messages_contextT, unextractedT, state, participantContext);
|
|
266
|
+
}
|
|
267
|
+
if (unextractedP.length > 0) {
|
|
268
|
+
queueRoomPersonScan(roomId, roomDisplayName, messages_contextP, unextractedP, state);
|
|
269
|
+
}
|
|
270
|
+
queueRoomEventScan(roomId, roomDisplayName, allVisible, state, participantContext);
|
|
271
|
+
|
|
272
|
+
console.log(`[queueRoomCapture] Queued extraction for room ${roomDisplayName}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function queuePersonaCapture(state: StateManager, personaId: string): void {
|
|
276
|
+
const persona = state.persona_getById(personaId);
|
|
277
|
+
if (!persona) return;
|
|
278
|
+
|
|
279
|
+
const allMessages = state.messages_get(personaId);
|
|
280
|
+
if (allMessages.length === 0) return;
|
|
281
|
+
|
|
282
|
+
const unextractedT = state.messages_getUnextracted(personaId, "t");
|
|
283
|
+
const unextractedP = state.messages_getUnextracted(personaId, "p");
|
|
284
|
+
const model = state.getHuman().settings?.default_model;
|
|
285
|
+
const options = { extraction_model: model };
|
|
286
|
+
|
|
287
|
+
if (unextractedT.length > 0) {
|
|
288
|
+
const analyzeStart = unextractedT[0].timestamp;
|
|
289
|
+
const messages_context = allMessages.filter(
|
|
290
|
+
m => m.t === true && new Date(m.timestamp).getTime() < new Date(analyzeStart).getTime()
|
|
291
|
+
);
|
|
292
|
+
const context: HumanExtractionContext = {
|
|
293
|
+
personaId,
|
|
294
|
+
personaDisplayName: persona.display_name,
|
|
295
|
+
messages_context,
|
|
296
|
+
messages_analyze: unextractedT,
|
|
297
|
+
};
|
|
298
|
+
queueTopicScan(context, state, options);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (unextractedP.length > 0) {
|
|
302
|
+
const analyzeStart = unextractedP[0].timestamp;
|
|
303
|
+
const messages_context = allMessages.filter(
|
|
304
|
+
m => m.p === true && new Date(m.timestamp).getTime() < new Date(analyzeStart).getTime()
|
|
305
|
+
);
|
|
306
|
+
const context: HumanExtractionContext = {
|
|
307
|
+
personaId,
|
|
308
|
+
personaDisplayName: persona.display_name,
|
|
309
|
+
messages_context,
|
|
310
|
+
messages_analyze: unextractedP,
|
|
311
|
+
};
|
|
312
|
+
queuePersonScan(context, state, options);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
queueEventSummary(personaId, state, options);
|
|
316
|
+
|
|
317
|
+
console.log(`[queuePersonaCapture] Queued extraction for persona ${persona.display_name}`);
|
|
318
|
+
}
|
|
@@ -65,11 +65,22 @@ export async function createPersona(
|
|
|
65
65
|
};
|
|
66
66
|
sm.persona_add(placeholder);
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
(
|
|
72
|
-
|
|
68
|
+
const hasTraits = input.traits && input.traits.length >= 3;
|
|
69
|
+
const hasTopics = input.topics && input.topics.length >= 3;
|
|
70
|
+
if (!hasTraits || !hasTopics) {
|
|
71
|
+
orchestratePersonaGeneration(
|
|
72
|
+
{ ...input, id: personaId },
|
|
73
|
+
sm,
|
|
74
|
+
() => onPersonaUpdated(placeholder.display_name)
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
sm.persona_update(personaId, {
|
|
78
|
+
traits: input.traits as PersonaEntity["traits"],
|
|
79
|
+
topics: input.topics as PersonaEntity["topics"],
|
|
80
|
+
last_updated: now,
|
|
81
|
+
});
|
|
82
|
+
onPersonaUpdated(placeholder.display_name);
|
|
83
|
+
}
|
|
73
84
|
|
|
74
85
|
return personaId;
|
|
75
86
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { PersonaEntity, Ei_Interface } from "../types.js";
|
|
2
|
+
import type { PersonaTrait } from "../types.js";
|
|
2
3
|
import type { StateManager } from "../state-manager.js";
|
|
3
4
|
import type { IOpenCodeReader } from "../../integrations/opencode/types.js";
|
|
4
5
|
import { AGENT_ALIASES } from "../../integrations/opencode/types.js";
|
|
5
6
|
import { createOpenCodeReader } from "../../integrations/opencode/reader-factory.js";
|
|
7
|
+
import { DEFAULT_SEED_TRAITS } from "../constants/seed-traits.js";
|
|
6
8
|
|
|
7
9
|
const OPENCODE_GROUP = "OpenCode";
|
|
8
10
|
const TWELVE_HOURS_MS = 43200000;
|
|
@@ -13,7 +15,7 @@ export interface EnsureAgentPersonaOptions {
|
|
|
13
15
|
reader?: IOpenCodeReader;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
function resolveCanonicalAgent(agentName: string): { canonical: string; aliases: string[] } {
|
|
18
|
+
export function resolveCanonicalAgent(agentName: string): { canonical: string; aliases: string[] } {
|
|
17
19
|
for (const [canonical, variants] of Object.entries(AGENT_ALIASES)) {
|
|
18
20
|
if (variants.includes(agentName)) {
|
|
19
21
|
return { canonical, aliases: variants };
|
|
@@ -47,6 +49,14 @@ export async function ensureAgentPersona(
|
|
|
47
49
|
|
|
48
50
|
const now = new Date().toISOString();
|
|
49
51
|
const personaId = crypto.randomUUID();
|
|
52
|
+
const seedTraits: PersonaTrait[] = DEFAULT_SEED_TRAITS.map((t) => ({
|
|
53
|
+
id: crypto.randomUUID(),
|
|
54
|
+
name: t.name,
|
|
55
|
+
description: t.description,
|
|
56
|
+
sentiment: t.sentiment,
|
|
57
|
+
strength: t.strength,
|
|
58
|
+
last_updated: now,
|
|
59
|
+
}));
|
|
50
60
|
const persona: PersonaEntity = {
|
|
51
61
|
id: personaId,
|
|
52
62
|
display_name: canonical,
|
|
@@ -56,7 +66,7 @@ export async function ensureAgentPersona(
|
|
|
56
66
|
long_description: "An OpenCode agent that assists with coding tasks.",
|
|
57
67
|
group_primary: OPENCODE_GROUP,
|
|
58
68
|
groups_visible: [OPENCODE_GROUP],
|
|
59
|
-
traits:
|
|
69
|
+
traits: seedTraits,
|
|
60
70
|
topics: [],
|
|
61
71
|
is_paused: false,
|
|
62
72
|
is_archived: false,
|