ei-tui 0.9.2 → 0.9.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/package.json +17 -1
- package/src/README.md +1 -1
- package/src/cli/commands/personas.ts +11 -2
- package/src/cli/mcp.ts +2 -2
- package/src/cli/retrieval.ts +40 -7
- package/src/cli.ts +63 -72
- package/src/core/context-utils.ts +2 -2
- package/src/core/handlers/heartbeat.ts +9 -1
- package/src/core/handlers/human-extraction.ts +4 -1
- package/src/core/handlers/human-matching.ts +5 -53
- package/src/core/handlers/index.ts +1 -51
- package/src/core/handlers/persona-generation.ts +1 -28
- package/src/core/handlers/utils.ts +2 -9
- package/src/core/heartbeat-manager.ts +4 -4
- package/src/core/message-manager.ts +6 -5
- package/src/core/orchestrators/ceremony.ts +15 -13
- package/src/core/orchestrators/extraction-chunker.ts +3 -3
- package/src/core/orchestrators/human-extraction.ts +27 -39
- package/src/core/orchestrators/index.ts +0 -1
- package/src/core/orchestrators/persona-topics.ts +1 -1
- package/src/core/orchestrators/room-extraction.ts +45 -7
- package/src/core/processor.ts +8 -21
- package/src/core/prompt-context-builder.ts +68 -36
- package/src/core/state/personas.ts +1 -17
- package/src/core/state-manager.ts +0 -66
- package/src/core/types/entities.ts +2 -3
- package/src/core/types/enums.ts +0 -2
- package/src/core/types/rooms.ts +1 -1
- package/src/integrations/claude-code/importer.ts +1 -1
- package/src/integrations/cursor/importer.ts +1 -1
- package/src/integrations/opencode/importer.ts +1 -1
- package/src/prompts/ceremony/index.ts +0 -10
- package/src/prompts/ceremony/types.ts +1 -42
- package/src/prompts/generation/index.ts +0 -3
- package/src/prompts/generation/types.ts +0 -15
- package/src/prompts/heartbeat/check.ts +18 -6
- package/src/prompts/heartbeat/types.ts +2 -1
- package/src/prompts/human/index.ts +0 -2
- package/src/prompts/human/person-update.ts +6 -23
- package/src/prompts/human/types.ts +0 -16
- package/src/prompts/index.ts +0 -19
- package/src/prompts/reflection/index.ts +36 -4
- package/src/prompts/reflection/types.ts +1 -1
- package/src/prompts/response/index.ts +5 -0
- package/src/prompts/response/sections.ts +26 -0
- package/src/prompts/response/types.ts +3 -0
- package/tui/src/commands/registry.test.ts +10 -5
- package/tui/src/globals.d.ts +57 -0
- package/tui/src/util/yaml-persona.ts +8 -4
- package/tui/src/util/yaml-settings.ts +3 -3
- package/src/core/orchestrators/person-migration.ts +0 -55
- package/src/prompts/ceremony/description-check.ts +0 -54
- package/src/prompts/ceremony/expire.ts +0 -37
- package/src/prompts/ceremony/explore.ts +0 -77
- package/src/prompts/ceremony/person-migration.ts +0 -77
- package/src/prompts/generation/descriptions.ts +0 -91
- package/src/prompts/human/fact-scan.ts +0 -150
|
@@ -47,24 +47,48 @@ function capTopicsAndPeople<T extends { id: string }, P extends { id: string }>(
|
|
|
47
47
|
// EMBEDDING-BASED RELEVANCE SELECTION
|
|
48
48
|
// =============================================================================
|
|
49
49
|
|
|
50
|
+
const RECENT_MESSAGES_FOR_CONTEXT = 5;
|
|
51
|
+
|
|
52
|
+
async function buildQueryVectors(queries: string[]): Promise<number[][]> {
|
|
53
|
+
const embeddingService = getEmbeddingService();
|
|
54
|
+
return Promise.all(queries.map(q => embeddingService.embed(q)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function unionTopK<T extends { id: string }>(
|
|
58
|
+
candidates: T[],
|
|
59
|
+
queryVectors: number[][],
|
|
60
|
+
limit: number
|
|
61
|
+
): T[] {
|
|
62
|
+
const best = new Map<string, { item: T; similarity: number }>();
|
|
63
|
+
for (const qv of queryVectors) {
|
|
64
|
+
for (const { item, similarity } of findTopK(qv, candidates, limit)) {
|
|
65
|
+
const existing = best.get(item.id);
|
|
66
|
+
if (!existing || similarity > existing.similarity) {
|
|
67
|
+
best.set(item.id, { item, similarity });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return Array.from(best.values())
|
|
72
|
+
.filter(({ similarity }) => similarity >= SIMILARITY_THRESHOLD)
|
|
73
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
74
|
+
.slice(0, limit)
|
|
75
|
+
.map(({ item }) => item);
|
|
76
|
+
}
|
|
77
|
+
|
|
50
78
|
async function selectRelevantItems<T extends { id: string; embedding?: number[] }>(
|
|
51
79
|
items: T[],
|
|
52
80
|
limit: number,
|
|
53
|
-
|
|
81
|
+
queries: string[]
|
|
54
82
|
): Promise<T[]> {
|
|
55
83
|
if (items.length === 0) return [];
|
|
56
84
|
|
|
57
85
|
const withEmbeddings = items.filter((i) => i.embedding?.length);
|
|
86
|
+
const activeQueries = queries.filter(Boolean);
|
|
58
87
|
|
|
59
|
-
if (
|
|
88
|
+
if (activeQueries.length > 0 && withEmbeddings.length > 0) {
|
|
60
89
|
try {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const results = findTopK(queryVector, withEmbeddings, limit);
|
|
64
|
-
const relevant = results
|
|
65
|
-
.filter(({ similarity }) => similarity >= SIMILARITY_THRESHOLD)
|
|
66
|
-
.map(({ item }) => item);
|
|
67
|
-
|
|
90
|
+
const queryVectors = await buildQueryVectors(activeQueries);
|
|
91
|
+
const relevant = unionTopK(withEmbeddings, queryVectors, limit);
|
|
68
92
|
if (relevant.length > 0) return relevant;
|
|
69
93
|
} catch (err) {
|
|
70
94
|
console.warn("[filterHumanDataByVisibility] Embedding search failed:", err);
|
|
@@ -80,19 +104,15 @@ async function selectRelevantItems<T extends { id: string; embedding?: number[]
|
|
|
80
104
|
.slice(0, limit);
|
|
81
105
|
}
|
|
82
106
|
|
|
83
|
-
async function selectRelevantQuotes(quotes: Quote[],
|
|
107
|
+
async function selectRelevantQuotes(quotes: Quote[], queries: string[]): Promise<Quote[]> {
|
|
84
108
|
if (quotes.length === 0) return [];
|
|
85
109
|
const withEmbeddings = quotes.filter((q) => q.embedding?.length);
|
|
110
|
+
const activeQueries = queries.filter(Boolean);
|
|
86
111
|
|
|
87
|
-
if (
|
|
112
|
+
if (activeQueries.length > 0 && withEmbeddings.length > 0) {
|
|
88
113
|
try {
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const results = findTopK(queryVector, withEmbeddings, QUOTE_LIMIT);
|
|
92
|
-
const relevant = results
|
|
93
|
-
.filter(({ similarity }) => similarity >= SIMILARITY_THRESHOLD)
|
|
94
|
-
.map(({ item }) => item);
|
|
95
|
-
|
|
114
|
+
const queryVectors = await buildQueryVectors(activeQueries);
|
|
115
|
+
const relevant = unionTopK(withEmbeddings, queryVectors, QUOTE_LIMIT);
|
|
96
116
|
if (relevant.length > 0) return relevant;
|
|
97
117
|
} catch (err) {
|
|
98
118
|
console.warn("[filterHumanDataByVisibility] Embedding search failed:", err);
|
|
@@ -110,16 +130,16 @@ async function selectRelevantQuotes(quotes: Quote[], currentMessage?: string): P
|
|
|
110
130
|
export async function filterHumanDataByVisibility(
|
|
111
131
|
human: HumanEntity,
|
|
112
132
|
persona: PersonaEntity,
|
|
113
|
-
|
|
133
|
+
queries: string[]
|
|
114
134
|
): Promise<ResponsePromptData["human"]> {
|
|
115
135
|
const DEFAULT_GROUP = "General";
|
|
116
136
|
|
|
117
137
|
if (persona.id === "ei") {
|
|
118
138
|
const [facts, rawTopics, rawPeople, quotes] = await Promise.all([
|
|
119
|
-
selectRelevantItems(human.facts, DATA_ITEM_LIMIT,
|
|
120
|
-
selectRelevantItems(human.topics, DATA_ITEM_LIMIT,
|
|
121
|
-
selectRelevantItems(human.people, DATA_ITEM_LIMIT,
|
|
122
|
-
selectRelevantQuotes(human.quotes ?? [],
|
|
139
|
+
selectRelevantItems(human.facts, DATA_ITEM_LIMIT, queries),
|
|
140
|
+
selectRelevantItems(human.topics, DATA_ITEM_LIMIT, queries),
|
|
141
|
+
selectRelevantItems(human.people, DATA_ITEM_LIMIT, queries),
|
|
142
|
+
selectRelevantQuotes(human.quotes ?? [], queries),
|
|
123
143
|
]);
|
|
124
144
|
const { topics, people } = capTopicsAndPeople(rawTopics, rawPeople);
|
|
125
145
|
const humanName =
|
|
@@ -157,10 +177,10 @@ export async function filterHumanDataByVisibility(
|
|
|
157
177
|
});
|
|
158
178
|
|
|
159
179
|
const [facts, rawTopics, rawPeople, quotes] = await Promise.all([
|
|
160
|
-
selectRelevantItems(filterByGroup(human.facts), DATA_ITEM_LIMIT,
|
|
161
|
-
selectRelevantItems(filterByGroup(human.topics), DATA_ITEM_LIMIT,
|
|
162
|
-
selectRelevantItems(filterByGroup(human.people), DATA_ITEM_LIMIT,
|
|
163
|
-
selectRelevantQuotes(groupFilteredQuotes,
|
|
180
|
+
selectRelevantItems(filterByGroup(human.facts), DATA_ITEM_LIMIT, queries),
|
|
181
|
+
selectRelevantItems(filterByGroup(human.topics), DATA_ITEM_LIMIT, queries),
|
|
182
|
+
selectRelevantItems(filterByGroup(human.people), DATA_ITEM_LIMIT, queries),
|
|
183
|
+
selectRelevantQuotes(groupFilteredQuotes, queries),
|
|
164
184
|
]);
|
|
165
185
|
const { topics, people } = capTopicsAndPeople(rawTopics, rawPeople);
|
|
166
186
|
|
|
@@ -233,14 +253,25 @@ export async function buildResponsePromptData(
|
|
|
233
253
|
tools?: import("./types.js").ToolDefinition[]
|
|
234
254
|
): Promise<ResponsePromptData> {
|
|
235
255
|
const human = sm.getHuman();
|
|
236
|
-
const filteredHuman = await filterHumanDataByVisibility(human, persona, currentMessage);
|
|
237
|
-
const visiblePersonas = getVisiblePersonas(sm, persona);
|
|
238
256
|
const messages = sm.messages_get(persona.id);
|
|
239
257
|
const previousMessage = messages.length >= 2 ? messages[messages.length - 2] : null;
|
|
240
258
|
const delayMs = previousMessage
|
|
241
259
|
? Date.now() - new Date(previousMessage.timestamp).getTime()
|
|
242
260
|
: 0;
|
|
243
261
|
|
|
262
|
+
const recentMessageContents = messages
|
|
263
|
+
.slice(-RECENT_MESSAGES_FOR_CONTEXT - 1, -1)
|
|
264
|
+
.map(m => getMessageContent(m))
|
|
265
|
+
.filter(Boolean);
|
|
266
|
+
|
|
267
|
+
const queries = [
|
|
268
|
+
...(currentMessage ? [currentMessage] : []),
|
|
269
|
+
...recentMessageContents,
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
const filteredHuman = await filterHumanDataByVisibility(human, persona, queries);
|
|
273
|
+
const visiblePersonas = getVisiblePersonas(sm, persona);
|
|
274
|
+
|
|
244
275
|
const alwaysMessages = sm.messages_getAlways(persona.id);
|
|
245
276
|
const temporalAnchors = alwaysMessages.map(m => ({
|
|
246
277
|
role: m.role === "human" ? "human" as const : "system" as const,
|
|
@@ -260,6 +291,7 @@ export async function buildResponsePromptData(
|
|
|
260
291
|
topics: persona.topics,
|
|
261
292
|
interested_topics: persona.topics.filter(t => t.exposure_desired - t.exposure_current > 0.2),
|
|
262
293
|
include_message_timestamps: persona.include_message_timestamps,
|
|
294
|
+
pending_update: persona.pending_update,
|
|
263
295
|
},
|
|
264
296
|
human: filteredHuman,
|
|
265
297
|
visible_personas: visiblePersonas,
|
|
@@ -287,10 +319,10 @@ export async function buildRoomResponsePromptData(
|
|
|
287
319
|
|
|
288
320
|
let sourceMessages: RoomMessage[];
|
|
289
321
|
if (room.mode === RoomMode.FreeForAll) {
|
|
290
|
-
const
|
|
291
|
-
?? human.settings?.
|
|
292
|
-
??
|
|
293
|
-
const windowCutoff = new Date(Date.now() -
|
|
322
|
+
const contextWindowMs = room.context_window_ms
|
|
323
|
+
?? human.settings?.default_context_window_ms
|
|
324
|
+
?? 28800000;
|
|
325
|
+
const windowCutoff = new Date(Date.now() - contextWindowMs).toISOString();
|
|
294
326
|
const boundaryMs = room.context_boundary ? new Date(room.context_boundary).getTime() : 0;
|
|
295
327
|
sourceMessages = allSourceMessages.filter(m => {
|
|
296
328
|
const msgMs = new Date(m.timestamp).getTime();
|
|
@@ -303,8 +335,8 @@ export async function buildRoomResponsePromptData(
|
|
|
303
335
|
const byCount = allSourceMessages.slice(-MIN_ROOM_MESSAGES);
|
|
304
336
|
if (byCount.length > sourceMessages.length) sourceMessages = byCount;
|
|
305
337
|
} else {
|
|
306
|
-
const
|
|
307
|
-
const windowCutoff = new Date(Date.now() -
|
|
338
|
+
const contextWindowMs = human.settings?.default_context_window_ms ?? 28800000;
|
|
339
|
+
const windowCutoff = new Date(Date.now() - contextWindowMs).toISOString();
|
|
308
340
|
const byTime = allSourceMessages.filter(m => m.timestamp >= windowCutoff);
|
|
309
341
|
const byCount = allSourceMessages.slice(-MIN_ROOM_MESSAGES);
|
|
310
342
|
sourceMessages = byTime.length >= byCount.length ? byTime : byCount;
|
|
@@ -313,7 +345,7 @@ export async function buildRoomResponsePromptData(
|
|
|
313
345
|
const lastMessage = sourceMessages[sourceMessages.length - 1];
|
|
314
346
|
const currentMessage = lastMessage ? getMessageContent(lastMessage) : undefined;
|
|
315
347
|
|
|
316
|
-
const filteredHuman = await filterHumanDataByVisibility(human, respondingPersona, currentMessage);
|
|
348
|
+
const filteredHuman = await filterHumanDataByVisibility(human, respondingPersona, currentMessage ? [currentMessage] : []);
|
|
317
349
|
|
|
318
350
|
const history = normalizeRoomMessages(sourceMessages, sm);
|
|
319
351
|
|
|
@@ -1,21 +1,5 @@
|
|
|
1
1
|
import type { PersonaEntity, Message, ContextStatus } from "../types.js";
|
|
2
2
|
|
|
3
|
-
// TODO(v1.0.0): Remove LegacyMessage migration — verbal_response/action_response no longer written
|
|
4
|
-
type LegacyMessage = Message & { verbal_response?: string; action_response?: string };
|
|
5
|
-
|
|
6
|
-
function migrateMessage(msg: Message): Message {
|
|
7
|
-
if (msg.content) return msg;
|
|
8
|
-
if (msg.silence_reason) return msg;
|
|
9
|
-
const legacy = msg as LegacyMessage;
|
|
10
|
-
const hasLegacy = 'verbal_response' in legacy || 'action_response' in legacy;
|
|
11
|
-
if (!hasLegacy) return msg;
|
|
12
|
-
const parts: string[] = [];
|
|
13
|
-
if (legacy.action_response) parts.push(`_${legacy.action_response}_`);
|
|
14
|
-
if (legacy.verbal_response) parts.push(legacy.verbal_response);
|
|
15
|
-
const { verbal_response: _vr, action_response: _ar, ...rest } = legacy;
|
|
16
|
-
return parts.length > 0 ? { ...rest, content: parts.join('\n\n') } : rest as Message;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
3
|
export interface PersonaData {
|
|
20
4
|
entity: PersonaEntity;
|
|
21
5
|
messages: Message[];
|
|
@@ -29,7 +13,7 @@ export class PersonaState {
|
|
|
29
13
|
this.personas = new Map(
|
|
30
14
|
Object.entries(personas).map(([id, data]) => [
|
|
31
15
|
id,
|
|
32
|
-
{ entity: data.entity, messages: data.messages
|
|
16
|
+
{ entity: data.entity, messages: data.messages },
|
|
33
17
|
])
|
|
34
18
|
);
|
|
35
19
|
}
|
|
@@ -69,76 +69,10 @@ export class StateManager {
|
|
|
69
69
|
this.migrateMessageFlags();
|
|
70
70
|
this.migrateInterestedPersonas();
|
|
71
71
|
this.migrateProviderModel();
|
|
72
|
-
this.migratePersonaMessageContent();
|
|
73
|
-
this.migrateRoomMessageContent();
|
|
74
72
|
this.migrateThemes();
|
|
75
73
|
this.migrateFfaParentIds();
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
private migratePersonaMessageContent(): void {
|
|
79
|
-
// TODO(v1.0.0): Remove legacy persona message migration — verbal_response/action_response no longer written
|
|
80
|
-
const rawPersonas = (this.personaState as unknown as { personas: Map<string, { messages: Message[] }> }).personas;
|
|
81
|
-
let migratedCount = 0;
|
|
82
|
-
for (const [, data] of rawPersonas) {
|
|
83
|
-
const messages = data.messages;
|
|
84
|
-
for (const msg of messages) {
|
|
85
|
-
const legacy = msg as Message & { verbal_response?: string; action_response?: string };
|
|
86
|
-
if (!('verbal_response' in legacy || 'action_response' in legacy)) continue;
|
|
87
|
-
if (msg.content) {
|
|
88
|
-
delete (legacy as any).verbal_response;
|
|
89
|
-
delete (legacy as any).action_response;
|
|
90
|
-
migratedCount++;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (msg.silence_reason) {
|
|
94
|
-
delete (legacy as any).verbal_response;
|
|
95
|
-
delete (legacy as any).action_response;
|
|
96
|
-
migratedCount++;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
const parts: string[] = [];
|
|
100
|
-
if (legacy.action_response) parts.push(`_${legacy.action_response}_`);
|
|
101
|
-
if (legacy.verbal_response) parts.push(legacy.verbal_response);
|
|
102
|
-
if (parts.length > 0) (msg as any).content = parts.join('\n\n');
|
|
103
|
-
delete (legacy as any).verbal_response;
|
|
104
|
-
delete (legacy as any).action_response;
|
|
105
|
-
migratedCount++;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (migratedCount > 0) {
|
|
109
|
-
this.scheduleSave();
|
|
110
|
-
console.log(`[StateManager] Migrated ${migratedCount} persona messages to unified content field`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private migrateRoomMessageContent(): void {
|
|
115
|
-
const rooms = this.roomState.getAll(true);
|
|
116
|
-
let migratedCount = 0;
|
|
117
|
-
|
|
118
|
-
for (const room of rooms) {
|
|
119
|
-
for (const msg of room.messages) {
|
|
120
|
-
if (msg.content) continue;
|
|
121
|
-
if (msg.silence_reason) continue;
|
|
122
|
-
// TODO(v1.0.0): Remove legacy room message migration — verbal_response/action_response no longer written
|
|
123
|
-
const legacy = msg as RoomMessage & { verbal_response?: string; action_response?: string };
|
|
124
|
-
const hasLegacy = 'verbal_response' in legacy || 'action_response' in legacy;
|
|
125
|
-
if (!hasLegacy) continue;
|
|
126
|
-
const parts: string[] = [];
|
|
127
|
-
if (legacy.action_response) parts.push(`_${legacy.action_response}_`);
|
|
128
|
-
if (legacy.verbal_response) parts.push(legacy.verbal_response);
|
|
129
|
-
if (parts.length > 0) msg.content = parts.join('\n\n');
|
|
130
|
-
delete (msg as any).verbal_response;
|
|
131
|
-
delete (msg as any).action_response;
|
|
132
|
-
migratedCount++;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (migratedCount > 0) {
|
|
137
|
-
this.scheduleSave();
|
|
138
|
-
console.log(`[StateManager] Migrated ${migratedCount} room messages to unified content field`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
76
|
/**
|
|
143
77
|
* Migration: learned_by used to store display names; now stores persona IDs.
|
|
144
78
|
* On load, attempt to resolve display names -> IDs using current persona map.
|
|
@@ -103,12 +103,11 @@ export interface HumanSettings {
|
|
|
103
103
|
default_model?: string; // Will store ModelConfig.id GUID post-migration
|
|
104
104
|
oneshot_model?: string; // Model for AI-assist (wand) requests; falls back to default_model. Will store ModelConfig.id GUID post-migration.
|
|
105
105
|
rewrite_model?: string; // Model for rewrite ceremony step; must be capable (Sonnet/Opus class). Unset = rewrite disabled. Will store ModelConfig.id GUID post-migration.
|
|
106
|
-
people_migration_complete?: boolean; // Set to true when all Person records have identifiers. Ceremony migration step short-circuits when true.
|
|
107
106
|
queue_paused?: boolean;
|
|
108
107
|
skip_quote_delete_confirm?: boolean;
|
|
109
108
|
name_display?: string;
|
|
110
109
|
default_heartbeat_ms?: number;
|
|
111
|
-
|
|
110
|
+
default_context_window_ms?: number;
|
|
112
111
|
message_min_count?: number;
|
|
113
112
|
message_max_age_days?: number;
|
|
114
113
|
accounts?: ProviderAccount[];
|
|
@@ -150,7 +149,7 @@ export interface PersonaEntity {
|
|
|
150
149
|
archived_at?: string;
|
|
151
150
|
is_static: boolean;
|
|
152
151
|
heartbeat_delay_ms?: number;
|
|
153
|
-
|
|
152
|
+
context_window_ms?: number;
|
|
154
153
|
include_message_timestamps?: boolean; // Prepend ISO timestamp to each message sent to the LLM
|
|
155
154
|
context_boundary?: string; // ISO timestamp - messages before this excluded from LLM context
|
|
156
155
|
last_updated: string;
|
package/src/core/types/enums.ts
CHANGED
|
@@ -26,7 +26,6 @@ export enum LLMPriority {
|
|
|
26
26
|
export enum LLMNextStep {
|
|
27
27
|
HandlePersonaResponse = "handlePersonaResponse",
|
|
28
28
|
HandlePersonaGeneration = "handlePersonaGeneration",
|
|
29
|
-
HandlePersonaDescriptions = "handlePersonaDescriptions",
|
|
30
29
|
HandleFactFind = "handleFactFind",
|
|
31
30
|
HandleHumanTopicScan = "handleHumanTopicScan",
|
|
32
31
|
HandleHumanPersonScan = "handleHumanPersonScan",
|
|
@@ -51,7 +50,6 @@ export enum LLMNextStep {
|
|
|
51
50
|
HandleRoomResponse = "handleRoomResponse",
|
|
52
51
|
HandleRoomJudge = "handleRoomJudge",
|
|
53
52
|
HandlePersonaPreview = "handlePersonaPreview",
|
|
54
|
-
HandlePersonIdentifierMigration = "handlePersonIdentifierMigration",
|
|
55
53
|
HandleTopicValidate = "handleTopicValidate",
|
|
56
54
|
HandleReflectionCritic = "handleReflectionCritic",
|
|
57
55
|
}
|
package/src/core/types/rooms.ts
CHANGED
|
@@ -35,7 +35,7 @@ export interface RoomEntity {
|
|
|
35
35
|
created_at: string;
|
|
36
36
|
last_updated: string;
|
|
37
37
|
capture_used?: boolean;
|
|
38
|
-
|
|
38
|
+
context_window_ms?: number; // FFA only; falls back to human.settings.default_context_window_ms
|
|
39
39
|
context_boundary?: string; // FFA only; ISO timestamp; same semantics as persona context_boundary
|
|
40
40
|
messages: RoomMessage[];
|
|
41
41
|
}
|
|
@@ -262,7 +262,7 @@ export async function importClaudeCodeSessions(
|
|
|
262
262
|
|
|
263
263
|
const context: ExtractionContext = {
|
|
264
264
|
personaId: persona.id,
|
|
265
|
-
|
|
265
|
+
channelDisplayName: persona.display_name,
|
|
266
266
|
messages_context: contextMsgs,
|
|
267
267
|
messages_analyze: toAnalyze,
|
|
268
268
|
sources: [`claudecode:${getMachineId()}:${targetSession.id}`],
|
|
@@ -221,7 +221,7 @@ export async function importCursorSessions(
|
|
|
221
221
|
|
|
222
222
|
const context: ExtractionContext = {
|
|
223
223
|
personaId: persona.id,
|
|
224
|
-
|
|
224
|
+
channelDisplayName: persona.display_name,
|
|
225
225
|
messages_context: contextMsgs,
|
|
226
226
|
messages_analyze: toAnalyze,
|
|
227
227
|
sources: [`cursor:${getMachineId()}:${targetSession.id}`],
|
|
@@ -245,7 +245,7 @@ export async function importOpenCodeSessions(
|
|
|
245
245
|
|
|
246
246
|
const context: ExtractionContext = {
|
|
247
247
|
personaId: persona.id,
|
|
248
|
-
|
|
248
|
+
channelDisplayName: persona.display_name,
|
|
249
249
|
messages_context: contextMsgs,
|
|
250
250
|
messages_analyze: toAnalyze,
|
|
251
251
|
sources: [`opencode:${getMachineId()}:${targetSession.id}`],
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
export { buildPersonaExpirePrompt } from "./expire.js";
|
|
2
|
-
export { buildPersonaExplorePrompt } from "./explore.js";
|
|
3
|
-
export { buildDescriptionCheckPrompt } from "./description-check.js";
|
|
4
1
|
export { buildRewriteScanPrompt, buildRewritePrompt } from "./rewrite.js";
|
|
5
2
|
export { buildDedupPrompt, buildValidatePrompt } from "./dedup.js";
|
|
6
3
|
export { buildUserDedupPrompt } from "./user-dedup.js";
|
|
7
|
-
export { buildPersonMigrationPrompt, type PersonMigrationPromptData } from "./person-migration.js";
|
|
8
4
|
export type {
|
|
9
|
-
PersonaExpirePromptData,
|
|
10
|
-
PersonaExpireResult,
|
|
11
|
-
PersonaExplorePromptData,
|
|
12
|
-
PersonaExploreResult,
|
|
13
|
-
DescriptionCheckPromptData,
|
|
14
|
-
DescriptionCheckResult,
|
|
15
5
|
RewriteItemType,
|
|
16
6
|
RewriteScanPromptData,
|
|
17
7
|
RewriteScanResult,
|
|
@@ -1,45 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export interface PersonaExpirePromptData {
|
|
4
|
-
persona_name: string;
|
|
5
|
-
topics: PersonaTopic[];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface PersonaExpireResult {
|
|
9
|
-
topic_ids_to_remove: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface PersonaExplorePromptData {
|
|
13
|
-
persona_name: string;
|
|
14
|
-
traits: PersonaTrait[];
|
|
15
|
-
remaining_topics: PersonaTopic[];
|
|
16
|
-
recent_conversation_themes: string[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface PersonaExploreResult {
|
|
20
|
-
new_topics: Array<{
|
|
21
|
-
name: string;
|
|
22
|
-
perspective: string;
|
|
23
|
-
approach: string;
|
|
24
|
-
personal_stake: string;
|
|
25
|
-
sentiment: number;
|
|
26
|
-
exposure_current: number;
|
|
27
|
-
exposure_desired: number;
|
|
28
|
-
}>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface DescriptionCheckPromptData {
|
|
32
|
-
persona_name: string;
|
|
33
|
-
current_short_description?: string;
|
|
34
|
-
current_long_description?: string;
|
|
35
|
-
traits: PersonaTrait[];
|
|
36
|
-
topics: PersonaTopic[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface DescriptionCheckResult {
|
|
40
|
-
should_update: boolean;
|
|
41
|
-
reason?: string;
|
|
42
|
-
}
|
|
1
|
+
import type { DataItemBase } from "../../core/types.js";
|
|
43
2
|
|
|
44
3
|
// =============================================================================
|
|
45
4
|
// REWRITE (Item Reorganization)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { buildPersonaGenerationPrompt } from "./persona.js";
|
|
2
|
-
export { buildPersonaDescriptionsPrompt } from "./descriptions.js";
|
|
3
2
|
export { buildPersonaFromPersonPrompt } from "./from-person.js";
|
|
4
3
|
export {
|
|
5
4
|
DEFAULT_SEED_TRAITS,
|
|
@@ -11,7 +10,5 @@ export type {
|
|
|
11
10
|
PersonaGenerationPromptData,
|
|
12
11
|
PersonaGenerationResult,
|
|
13
12
|
PersonaFromPersonPromptData,
|
|
14
|
-
PersonaDescriptionsPromptData,
|
|
15
|
-
PersonaDescriptionsResult,
|
|
16
13
|
PromptOutput,
|
|
17
14
|
} from "./types.js";
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { PersonaTrait, PersonaTopic } from "../../core/types.js";
|
|
2
|
-
|
|
3
1
|
export interface PromptOutput {
|
|
4
2
|
system: string;
|
|
5
3
|
user: string;
|
|
@@ -45,16 +43,3 @@ export interface PersonaFromPersonPromptData {
|
|
|
45
43
|
existing_trait_names?: string[];
|
|
46
44
|
existing_topic_names?: string[];
|
|
47
45
|
}
|
|
48
|
-
|
|
49
|
-
export interface PersonaDescriptionsPromptData {
|
|
50
|
-
name: string;
|
|
51
|
-
aliases: string[];
|
|
52
|
-
traits: PersonaTrait[];
|
|
53
|
-
topics: PersonaTopic[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface PersonaDescriptionsResult {
|
|
57
|
-
short_description: string;
|
|
58
|
-
long_description: string;
|
|
59
|
-
no_change?: boolean;
|
|
60
|
-
}
|
|
@@ -74,7 +74,6 @@ function getLastPersonaMessage(history: Message[]): Message | undefined {
|
|
|
74
74
|
* - Getting recent message history
|
|
75
75
|
*/
|
|
76
76
|
export function buildHeartbeatCheckPrompt(data: HeartbeatCheckPromptData): PromptOutput {
|
|
77
|
-
console.log(`[HeartbeatCheck ${data.persona.name}] Building prompt - topics: ${data.human.topics.length}, people: ${data.human.people.length}, inactive_days: ${data.inactive_days}, history: ${data.recent_history.length} messages`);
|
|
78
77
|
if (!data.persona?.name) {
|
|
79
78
|
throw new Error("buildHeartbeatCheckPrompt: persona.name is required");
|
|
80
79
|
}
|
|
@@ -124,11 +123,24 @@ ${formatPeopleWithGaps(data.human.people)}`;
|
|
|
124
123
|
|
|
125
124
|
**Quality over quantity** - Only reach out if you have something real to say.`;
|
|
126
125
|
|
|
127
|
-
const pendingUpdateFragment = data.persona.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
const pendingUpdateFragment = data.persona.pending_update ? (() => {
|
|
127
|
+
const p = data.persona.pending_update!;
|
|
128
|
+
const descPart = p.long_description || p.short_description
|
|
129
|
+
? `### Proposed Description\n${p.long_description || p.short_description}`
|
|
130
|
+
: "";
|
|
131
|
+
const traitsPart = p.traits.length > 0
|
|
132
|
+
? `### Proposed Traits\n${p.traits.map(t => `- **${t.name}**: ${t.description}`).join("\n")}`
|
|
133
|
+
: "";
|
|
134
|
+
const topicsPart = p.topics.length > 0
|
|
135
|
+
? `### Proposed Interests\n${p.topics.map(t => `- **${t.name}**: ${t.perspective}`).join("\n")}`
|
|
136
|
+
: "";
|
|
137
|
+
const parts = [descPart, traitsPart, topicsPart].filter(Boolean).join("\n\n");
|
|
138
|
+
return `## Pending Identity Changes
|
|
139
|
+
|
|
140
|
+
Your human is reviewing proposed updates to your identity. These are waiting for their response — you may want to bring it up, or not. It's yours to decide.
|
|
141
|
+
|
|
142
|
+
${parts}`;
|
|
143
|
+
})() : '';
|
|
132
144
|
|
|
133
145
|
const outputFragment = `## Response Format
|
|
134
146
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { PersonaTrait, Topic, Person, Message, PersonaTopic } from "../../core/types.js";
|
|
7
|
+
import type { PersonaEntity } from "../../core/types/entities.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Common prompt output structure
|
|
@@ -21,7 +22,7 @@ export interface HeartbeatCheckPromptData {
|
|
|
21
22
|
name: string;
|
|
22
23
|
traits: PersonaTrait[];
|
|
23
24
|
topics: PersonaTopic[];
|
|
24
|
-
|
|
25
|
+
pending_update?: PersonaEntity["pending_update"];
|
|
25
26
|
};
|
|
26
27
|
human: {
|
|
27
28
|
topics: Topic[]; // Filtered, sorted by engagement gap
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { buildHumanFactScanPrompt } from "./fact-scan.js";
|
|
2
1
|
export { buildFactFindPrompt } from "./fact-find.js";
|
|
3
2
|
export { buildHumanTopicScanPrompt } from "./topic-scan.js";
|
|
4
3
|
export { buildHumanPersonScanPrompt } from "./person-scan.js";
|
|
@@ -16,7 +15,6 @@ export type {
|
|
|
16
15
|
PromptOutput,
|
|
17
16
|
ParticipantContext,
|
|
18
17
|
PersonaEntitySnapshot,
|
|
19
|
-
FactScanPromptData,
|
|
20
18
|
TopicScanPromptData,
|
|
21
19
|
PersonScanPromptData,
|
|
22
20
|
FactFindPromptData,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PromptOutput, ParticipantContext
|
|
1
|
+
import type { PromptOutput, ParticipantContext } from "./types.js";
|
|
2
2
|
import type { Person, Message } from "../../core/types.js";
|
|
3
3
|
import { formatMessagesAsPlaceholders } from "../message-utils.js";
|
|
4
4
|
|
|
@@ -12,7 +12,6 @@ export interface PersonUpdatePromptData {
|
|
|
12
12
|
persona_name: string;
|
|
13
13
|
participant_context?: ParticipantContext;
|
|
14
14
|
known_identifier_types?: string[];
|
|
15
|
-
persona_entity?: PersonaEntitySnapshot;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
function participantContextSection(ctx: ParticipantContext | undefined): string {
|
|
@@ -121,38 +120,22 @@ Detail you add should:
|
|
|
121
120
|
**ABSOLUTELY VITAL**: Do **NOT** embellish. Record only what the user actually said or demonstrated.`;
|
|
122
121
|
|
|
123
122
|
} else if (isEiPersona) {
|
|
124
|
-
|
|
125
|
-
? `## This Persona's Defined Identity (for reference)
|
|
126
|
-
|
|
127
|
-
The following is ${personName}'s current self-definition — their traits, topics, and long description as set in the Persona editor. Use this as a **baseline**, not a ceiling.
|
|
128
|
-
|
|
129
|
-
**Long description:**
|
|
130
|
-
${data.persona_entity.long_description}
|
|
131
|
-
|
|
132
|
-
**Traits:**
|
|
133
|
-
${data.persona_entity.traits.map(t => `- **${t.name}**: ${t.description}`).join('\n')}
|
|
134
|
-
|
|
135
|
-
**Topics:**
|
|
136
|
-
${data.persona_entity.topics.map(t => `- **${t.name}**: ${t.perspective}`).join('\n')}
|
|
137
|
-
|
|
138
|
-
`
|
|
139
|
-
: '';
|
|
140
|
-
|
|
141
|
-
descriptionSection = `${entityRef}This record is the HUMAN USER's **observed experience** of ${personName} over time — not the Persona's own definition. Think of it as field notes from someone who has been talking with this Persona across many conversations.
|
|
123
|
+
descriptionSection = `This record is the HUMAN USER's **observed experience** of ${personName} over time. Think of it as field notes from someone who has been talking with this Persona across many conversations.
|
|
142
124
|
|
|
143
125
|
## Your job: add, never truncate
|
|
144
126
|
|
|
145
127
|
The description is allowed to grow. **Never remove or summarize away existing content.**
|
|
146
128
|
|
|
147
129
|
Add anything from the Most Recent Messages that:
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
130
|
+
- Records a specific thing ${personName} said, did, or demonstrated in this conversation
|
|
131
|
+
- Captures how the HUMAN USER experienced or related to ${personName} in this specific exchange
|
|
132
|
+
- Notes behavior that stands out — expected or surprising — based on what the existing log already shows
|
|
151
133
|
|
|
152
134
|
**Do NOT:**
|
|
153
135
|
- Synthesize the existing description down to fewer sentences
|
|
154
136
|
- Replace specific observations with vague summaries
|
|
155
137
|
- Discard detail to "keep it brief" — brevity is wrong here
|
|
138
|
+
- Add anything that isn't directly observed in the Most Recent Messages
|
|
156
139
|
|
|
157
140
|
If the new messages add nothing meaningful, return \`{}\`. Otherwise, return the **full updated description** — existing content preserved, new observations woven in.`;
|
|
158
141
|
|
|
@@ -25,10 +25,6 @@ interface BaseScanPromptData {
|
|
|
25
25
|
persona_name: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export interface FactScanPromptData extends BaseScanPromptData {}
|
|
29
|
-
|
|
30
|
-
export interface TraitScanPromptData extends BaseScanPromptData {}
|
|
31
|
-
|
|
32
28
|
export interface TopicScanPromptData extends BaseScanPromptData {
|
|
33
29
|
participant_context?: ParticipantContext;
|
|
34
30
|
}
|
|
@@ -78,18 +74,6 @@ export interface TopicMatchPromptData {
|
|
|
78
74
|
}>;
|
|
79
75
|
}
|
|
80
76
|
|
|
81
|
-
export interface PersonMatchPromptData {
|
|
82
|
-
candidate_name: string;
|
|
83
|
-
candidate_description: string;
|
|
84
|
-
candidate_relationship: string;
|
|
85
|
-
existing_people: Array<{
|
|
86
|
-
id: string;
|
|
87
|
-
name: string;
|
|
88
|
-
description: string;
|
|
89
|
-
relationship?: string;
|
|
90
|
-
}>;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
77
|
export interface FactScanResult {
|
|
94
78
|
facts: FactScanCandidate[];
|
|
95
79
|
}
|