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,4 @@
|
|
|
1
|
-
import type { PromptOutput } from "./types.js";
|
|
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
|
|
|
@@ -10,11 +10,26 @@ export interface PersonUpdatePromptData {
|
|
|
10
10
|
messages_context: Message[];
|
|
11
11
|
messages_analyze: Message[];
|
|
12
12
|
persona_name: string;
|
|
13
|
+
participant_context?: ParticipantContext;
|
|
14
|
+
known_identifier_types?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function participantContextSection(ctx: ParticipantContext | undefined): string {
|
|
18
|
+
if (!ctx) return "";
|
|
19
|
+
const lines: string[] = ["# Participant Context", "The following may help you understand who is in this conversation.", ""];
|
|
20
|
+
lines.push(`## Persona: ${ctx.persona_name}`);
|
|
21
|
+
if (ctx.persona_description) lines.push(ctx.persona_description);
|
|
22
|
+
lines.push("");
|
|
23
|
+
lines.push("## Human User");
|
|
24
|
+
if (ctx.human_name) lines.push(`Name: ${ctx.human_name}`);
|
|
25
|
+
if (ctx.human_age !== undefined) lines.push(`Age: ${ctx.human_age}`);
|
|
26
|
+
lines.push("");
|
|
27
|
+
return lines.join("\n");
|
|
13
28
|
}
|
|
14
29
|
|
|
15
30
|
function formatExistingPerson(person: Person): string {
|
|
16
31
|
return JSON.stringify({
|
|
17
|
-
|
|
32
|
+
identifiers: person.identifiers ?? [],
|
|
18
33
|
description: person.description,
|
|
19
34
|
sentiment: person.sentiment,
|
|
20
35
|
relationship: person.relationship,
|
|
@@ -29,118 +44,159 @@ export function buildPersonUpdatePrompt(data: PersonUpdatePromptData): PromptOut
|
|
|
29
44
|
}
|
|
30
45
|
|
|
31
46
|
const personaName = data.persona_name;
|
|
47
|
+
const isNewItem = data.existing_item === null;
|
|
48
|
+
const humanName = data.participant_context?.human_name;
|
|
49
|
+
|
|
50
|
+
const builtInTypes = ['Full Name', 'First Name', 'Nickname', 'Email', 'GitHub', 'Discord',
|
|
51
|
+
'Roblox', 'Reddit', 'Twitter', 'FF14', 'Relationship', 'Ei Persona'];
|
|
52
|
+
const userTypes = data.known_identifier_types ?? [];
|
|
53
|
+
const allTypes = [...new Set([...builtInTypes, ...userTypes])].join(', ');
|
|
54
|
+
|
|
55
|
+
const personRelationship = data.existing_item?.relationship ?? data.new_person_relationship ?? 'Unknown';
|
|
56
|
+
const personName = data.existing_item?.name ?? data.new_person_name ?? 'Unknown';
|
|
57
|
+
|
|
58
|
+
// ── WHO YOU ARE ANALYZING ──────────────────────────────────────────────────
|
|
59
|
+
// This section anchors the model on the target person before anything else.
|
|
60
|
+
// Local models lose context from long prompts — this must be first.
|
|
61
|
+
|
|
62
|
+
const personFocusSection = isNewItem
|
|
63
|
+
? `# WHO YOU ARE ANALYZING
|
|
64
|
+
|
|
65
|
+
NEW PERSON — not yet in system
|
|
66
|
+
Relationship: ${personRelationship}
|
|
67
|
+
Name: ${personName === 'Unknown' ? 'Not yet known' : personName}${data.new_person_description ? `\nWhat we know: ${data.new_person_description}` : ''}
|
|
68
|
+
|
|
69
|
+
Your ONLY job is to find information about the HUMAN USER's **${personRelationship}** in the conversation and create their record. Ignore all other people mentioned — they each have their own separate records.`
|
|
70
|
+
: `# WHO YOU ARE ANALYZING
|
|
71
|
+
|
|
72
|
+
EXISTING PERSON RECORD — update only if the Most Recent Messages contain new information
|
|
73
|
+
Name: ${personName}
|
|
74
|
+
Relationship: ${personRelationship}
|
|
75
|
+
|
|
76
|
+
Current record:
|
|
77
|
+
\`\`\`json
|
|
78
|
+
${formatExistingPerson(data.existing_item!)}
|
|
79
|
+
\`\`\`
|
|
32
80
|
|
|
33
|
-
|
|
81
|
+
Your ONLY job is to update THIS SPECIFIC PERSON's record based on the Most Recent Messages. Ignore all other people mentioned — they each have their own separate records.`;
|
|
34
82
|
|
|
35
|
-
|
|
83
|
+
// ── TASK ──────────────────────────────────────────────────────────────────
|
|
36
84
|
|
|
37
|
-
|
|
85
|
+
const taskSection = isNewItem
|
|
86
|
+
? `# Task
|
|
38
87
|
|
|
39
|
-
|
|
88
|
+
You are creating a new PERSON record based on what you find about the HUMAN USER's **${personRelationship}** in the conversation.
|
|
89
|
+
|
|
90
|
+
The record you create will be referenced by personas in future conversations — keep it accurate, brief, and useful.`
|
|
91
|
+
: `# Task
|
|
92
|
+
|
|
93
|
+
You are scanning a conversation to update a PERSON record in the HUMAN USER's life.
|
|
94
|
+
|
|
95
|
+
Apply changes to the record above **ONLY IF DOING SO WILL PROVIDE THE HUMAN USER WITH A BETTER EXPERIENCE IN THE FUTURE**.
|
|
96
|
+
|
|
97
|
+
Detail you add should:
|
|
98
|
+
1. Be meaningful, accurate, or still true to the HUMAN USER in six months or more
|
|
99
|
+
2. **NOT** already be present in the record above`;
|
|
100
|
+
|
|
101
|
+
// ── DESCRIPTION SECTION ───────────────────────────────────────────────────
|
|
102
|
+
// New: shorter, no "don't accumulate" guidance (nothing to accumulate yet)
|
|
103
|
+
// Existing: full synthesis guidance
|
|
104
|
+
|
|
105
|
+
const descriptionSection = isNewItem
|
|
106
|
+
? `A concise summary of who this person is and how they relate to the HUMAN USER. Keep it brief and factual — only what you can confirm from the conversation.
|
|
107
|
+
|
|
108
|
+
- Capture who this person IS — their role in the user's life
|
|
109
|
+
- Be useful to a persona who's never heard this person's name before
|
|
110
|
+
- 1-3 sentences maximum
|
|
111
|
+
- If you know their birth date or birth year, include it as a date (e.g. "born 1986-10-28") — never as a current age (ages change, dates don't)
|
|
112
|
+
|
|
113
|
+
**ABSOLUTELY VITAL**: Do **NOT** embellish. Record only what the user actually said or demonstrated.`
|
|
114
|
+
: `A concise summary of who this person is and how they relate to the HUMAN USER. Personas use this to recognize this person and engage meaningfully when they come up.
|
|
40
115
|
|
|
41
116
|
## CRITICAL: Synthesize, don't accumulate
|
|
42
117
|
|
|
43
118
|
Every update must **rewrite** the description as a current-state summary. Never append to it.
|
|
44
119
|
|
|
45
|
-
**Good
|
|
120
|
+
**Good**: "Borfinda, partner of 12 years. Former marine biologist, now stay-at-home parent. Tends to ground the user when they spiral; dry sense of humor. Two kids together."
|
|
46
121
|
|
|
47
|
-
**Bad
|
|
122
|
+
**Bad**: "Borfinda was mentioned when the user talked about moving. In a later conversation she came up again during the work stress discussion. Most recently the user said she was supportive."
|
|
48
123
|
|
|
49
124
|
The description should:
|
|
50
125
|
- Capture who this person IS — their role, characteristics, relationship texture
|
|
51
126
|
- Include what the HUMAN USER has revealed about them over time
|
|
52
|
-
- Be useful to a persona who's never heard this person's name before
|
|
53
127
|
- Read as a brief, confident summary — not a log of when they were mentioned
|
|
128
|
+
- Not exceed 3-4 sentences
|
|
54
129
|
|
|
55
130
|
The description should NOT:
|
|
56
131
|
- Append "Most recently:", "Latest mention:", or any temporal marker
|
|
57
132
|
- Accumulate a session-by-session history of every time this person came up
|
|
58
133
|
- Speculate about the person based on thin evidence
|
|
59
|
-
-
|
|
134
|
+
- Record someone's age — record their birth date or birth year instead (e.g. "born 1981" not "age 44")
|
|
60
135
|
|
|
61
136
|
**ABSOLUTELY VITAL**: Do **NOT** embellish — personas use their own voice. Record what the user actually said or demonstrated, not your interpretation of its emotional significance.`;
|
|
62
137
|
|
|
63
|
-
|
|
138
|
+
// ── IDENTIFIERS ───────────────────────────────────────────────────────────
|
|
64
139
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Keep it concise and specific. Avoid vague labels.
|
|
70
|
-
|
|
71
|
-
Examples: "Unknown" → "Coworker", "Mother" → "Step-Mother", "Fiance" → "Spouse", "AI Persona" → "AI Companion"`;
|
|
140
|
+
const identityGuard = humanName
|
|
141
|
+
? `CRITICAL: The HUMAN USER is ${humanName}. They wrote these messages. Do NOT assign their names, nicknames, or handles as identifiers for this person's record — UNLESS this IS the user's own Self record (relationship: "Self").`
|
|
142
|
+
: `CRITICAL: The HUMAN USER wrote these messages. Do NOT assign their own names or handles as identifiers for this person's record — UNLESS this IS the user's own Self record (relationship: "Self"). Do NOT return \`relationship: "Self"\` unless you are certain this record is about the human user themselves.`;
|
|
72
143
|
|
|
73
|
-
const
|
|
144
|
+
const isUnknownNewPerson = isNewItem && personName === 'Unknown';
|
|
145
|
+
const unknownIdentifierGuard = isUnknownNewPerson
|
|
146
|
+
? `\nThis person's name is not yet known. ONLY add \`identifiers\` if their name, handle, or email is explicitly stated in the conversation about THEM specifically — not inferred, not guessed.`
|
|
147
|
+
: '';
|
|
74
148
|
|
|
75
|
-
|
|
149
|
+
const identifierSection = `${identityGuard}${unknownIdentifierGuard}
|
|
76
150
|
|
|
77
|
-
|
|
78
|
-
- 0.0: Never wants to hear about this PERSON again
|
|
79
|
-
- 0.5: Average amount of engagement
|
|
80
|
-
- 1.0: This PERSON is the sole focus of their existence
|
|
151
|
+
If you spot a platform handle, username, email, nickname, or full name explicitly mentioned in the conversation that isn't already in the person's identifiers, include it in \`identifiers_to_add\` (updates) or \`identifiers\` (new records). Always mark exactly one identifier as \`"is_primary": true\` — prefer the most formal or complete name.
|
|
81
152
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
## Exposure Impact (\`exposure_impact\`)
|
|
85
|
-
|
|
86
|
-
Not in the current data — but include it in your response.
|
|
87
|
-
|
|
88
|
-
How much this conversation should count toward exposure tracking:
|
|
89
|
-
- "high": Long, detailed conversation exclusively about this PERSON
|
|
90
|
-
- "medium": Long OR detailed conversation about this PERSON
|
|
91
|
-
- "low": The conversation touched on this PERSON briefly
|
|
92
|
-
- "none": Only alluded to or hinted at`;
|
|
93
|
-
|
|
94
|
-
const currentDetailsSection = data.existing_item
|
|
95
|
-
? `\`\`\`json
|
|
96
|
-
${formatExistingPerson(data.existing_item)}
|
|
97
|
-
\`\`\`
|
|
153
|
+
For persons with a known relationship (Father, Mother, Sibling, etc.), also look for informal terms the HUMAN USER uses to address or refer to THAT SPECIFIC PERSON (\`Dad\`, \`Pop\`, \`Mom\`, \`Sis\`, etc.) and add them as \`{ "type": "Relationship", "value": "..." }\` identifiers.
|
|
98
154
|
|
|
99
|
-
|
|
100
|
-
: `**NEW PERSON — NOT YET IN SYSTEM**
|
|
155
|
+
NEVER add dates, ages, birthdays, or anniversaries as identifiers. These are not identifying labels — if known, include them in the description instead.
|
|
101
156
|
|
|
102
|
-
|
|
103
|
-
\`\`\`json
|
|
104
|
-
{
|
|
105
|
-
"name": "${data.new_person_name ?? "Unknown"}",
|
|
106
|
-
"description": "${data.new_person_description ?? "Details unknown"}",
|
|
107
|
-
"relationship": "${data.new_person_relationship ?? "Unknown"}"
|
|
108
|
-
}
|
|
109
|
-
\`\`\`
|
|
157
|
+
Known identifier types: ${allTypes}. If unsure of type, use \`Nickname\`.`;
|
|
110
158
|
|
|
111
|
-
|
|
159
|
+
// ── OUTPUT FORMAT ─────────────────────────────────────────────────────────
|
|
112
160
|
|
|
113
|
-
const jsonTemplate =
|
|
114
|
-
|
|
161
|
+
const jsonTemplate = isNewItem
|
|
162
|
+
? `{
|
|
163
|
+
"identifiers": [
|
|
164
|
+
{ "type": "Full Name", "value": "Borfinda Lastname", "is_primary": true },
|
|
165
|
+
{ "type": "Nickname", "value": "Borfi" }
|
|
166
|
+
],
|
|
167
|
+
"description": "...",
|
|
168
|
+
"sentiment": 0.0,
|
|
169
|
+
"relationship": "Mother|Friend|Coworker|AI Companion|etc.",
|
|
170
|
+
"exposure_desired": 0.5,
|
|
171
|
+
"exposure_impact": "high|medium|low|none",
|
|
172
|
+
"quotes": [
|
|
173
|
+
{ "text": "exact phrase from message", "reason": "why this matters" }
|
|
174
|
+
]
|
|
175
|
+
}`
|
|
176
|
+
: `{
|
|
177
|
+
"identifiers_to_add": [{ "type": "GitHub", "value": "handle" }],
|
|
115
178
|
"description": "...",
|
|
116
179
|
"sentiment": 0.0,
|
|
117
180
|
"relationship": "Mother|Friend|Coworker|AI Companion|etc.",
|
|
118
181
|
"exposure_desired": 0.5,
|
|
119
182
|
"exposure_impact": "high|medium|low|none",
|
|
120
183
|
"quotes": [
|
|
121
|
-
{
|
|
122
|
-
"text": "exact phrase from message",
|
|
123
|
-
"reason": "why this matters"
|
|
124
|
-
}
|
|
184
|
+
{ "text": "exact phrase from message", "reason": "why this matters" }
|
|
125
185
|
]
|
|
126
186
|
}`;
|
|
127
187
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
188
|
+
// ── SYSTEM PROMPT ASSEMBLY ─────────────────────────────────────────────────
|
|
189
|
+
// Order: WHO (anchor) → task → participant context → field definitions →
|
|
190
|
+
// output format. Person details are in WHO, not repeated at the bottom.
|
|
131
191
|
|
|
132
|
-
|
|
192
|
+
const system = `${personFocusSection}
|
|
133
193
|
|
|
134
|
-
|
|
135
|
-
1. Be meaningful, accurate, or still true to the HUMAN USER in six months or more
|
|
136
|
-
2. **NOT** already be present in the description or name of the PERSON
|
|
194
|
+
${taskSection}
|
|
137
195
|
|
|
138
|
-
|
|
196
|
+
${participantContextSection(data.participant_context)}# Field Definitions
|
|
139
197
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
## Name (\`name\`)
|
|
143
|
-
${nameSection}
|
|
198
|
+
## Identifiers
|
|
199
|
+
${identifierSection}
|
|
144
200
|
|
|
145
201
|
## Description (\`description\`)
|
|
146
202
|
${descriptionSection}
|
|
@@ -158,40 +214,55 @@ Scale of -1.0 to 1.0:
|
|
|
158
214
|
|
|
159
215
|
Do not make micro-adjustments. Close enough is OK.
|
|
160
216
|
|
|
161
|
-
|
|
217
|
+
## Relationship (\`relationship\`)
|
|
162
218
|
|
|
163
|
-
|
|
219
|
+
How the HUMAN USER is currently related to this PERSON.
|
|
164
220
|
|
|
165
|
-
|
|
221
|
+
Once known, this field changes infrequently — a "Father" may later be clarified to "Step-Father", but is unlikely to become "Uncle".
|
|
222
|
+
|
|
223
|
+
Keep it concise and specific. Avoid vague labels.
|
|
224
|
+
|
|
225
|
+
Examples: "Unknown" → "Coworker", "Mother" → "Step-Mother", "Fiance" → "Spouse", "AI Persona" → "AI Companion"
|
|
226
|
+
|
|
227
|
+
## Desired Exposure (\`exposure_desired\`)
|
|
228
|
+
|
|
229
|
+
How much the HUMAN USER wants to talk about this PERSON.
|
|
230
|
+
|
|
231
|
+
Scale of 0.0 to 1.0:
|
|
232
|
+
- 0.0: Never wants to hear about this PERSON again
|
|
233
|
+
- 0.5: Average amount of engagement
|
|
234
|
+
- 1.0: This PERSON is the sole focus of their existence
|
|
235
|
+
|
|
236
|
+
Do not make micro-adjustments. Close enough is OK.
|
|
166
237
|
|
|
167
|
-
|
|
238
|
+
## Exposure Impact (\`exposure_impact\`)
|
|
168
239
|
|
|
169
|
-
|
|
240
|
+
Not in the current data — but include it in your response.
|
|
241
|
+
|
|
242
|
+
How much this conversation should count toward exposure tracking:
|
|
243
|
+
- "high": Long, detailed conversation exclusively about this PERSON
|
|
244
|
+
- "medium": Long OR detailed conversation about this PERSON
|
|
245
|
+
- "low": The conversation touched on this PERSON briefly
|
|
246
|
+
- "none": Only alluded to or hinted at
|
|
247
|
+
|
|
248
|
+
## Quotes
|
|
249
|
+
|
|
250
|
+
Identify any **memorable, funny, important, or stand-out phrases** from the Most Recent Messages that relate to this PERSON.
|
|
170
251
|
|
|
171
252
|
**Prioritize:**
|
|
172
253
|
- Humor, wit, colorful language, creative profanity
|
|
173
254
|
- Emotional outbursts (positive or negative) — the raw stuff
|
|
174
255
|
- Phrases that reveal how the HUMAN USER feels about this PERSON
|
|
175
256
|
- Things you'd quote back to them later to make them laugh or think
|
|
176
|
-
- Unique expressions or turns of phrase about or from this PERSON
|
|
177
|
-
- Quotable moments from EITHER speaker — humans AND AI personas both say memorable things
|
|
178
257
|
|
|
179
|
-
**NEVER extract these
|
|
258
|
+
**NEVER extract these:**
|
|
180
259
|
- Technical identifiers: ARNs, URLs, file paths, UUIDs, config keys
|
|
181
|
-
- AI agent self-talk
|
|
182
|
-
- AI apologies or acknowledgments: "You're absolutely right", "I apologize for that"
|
|
260
|
+
- AI agent self-talk or apologies
|
|
183
261
|
- Generic statements that could apply to anyone
|
|
184
|
-
- Credentials, secrets, connection strings, or anything that looks like an access token
|
|
185
|
-
|
|
186
|
-
**The litmus test**: Would you bring this up at a bar with a friend? Would it make someone laugh, think, or feel something?
|
|
187
|
-
- "She's the only person who can make me feel simultaneously stupid and brilliant" → YES.
|
|
188
|
-
- "Borfinda was mentioned in the context of the Minnesota discussion" → NO. That's a note, not a quote.
|
|
189
|
-
|
|
190
|
-
**When in doubt, leave it out.** An empty quotes array is always acceptable.
|
|
191
262
|
|
|
192
|
-
**CRITICAL**: Return the EXACT text as it appears in the message.
|
|
263
|
+
**CRITICAL**: Return the EXACT text as it appears in the message.
|
|
193
264
|
|
|
194
|
-
#
|
|
265
|
+
# Output
|
|
195
266
|
|
|
196
267
|
ONLY ANALYZE the "Most Recent Messages". The "Earlier Conversation" is provided for context only — it has already been processed.
|
|
197
268
|
|
|
@@ -199,19 +270,17 @@ ONLY ANALYZE the "Most Recent Messages". The "Earlier Conversation" is provided
|
|
|
199
270
|
${jsonTemplate}
|
|
200
271
|
\`\`\`
|
|
201
272
|
|
|
202
|
-
When returning a record, **ALWAYS** include \`
|
|
273
|
+
When returning a record, **ALWAYS** include \`description\` and \`sentiment\`. Do NOT return \`relationship: "Self"\` unless this record IS the human user themselves.
|
|
203
274
|
|
|
204
|
-
If you find **NO EVIDENCE** of
|
|
275
|
+
If you find **NO EVIDENCE** of the HUMAN USER's **${personRelationship}** in the "Most Recent Messages", respond with: \`{}\`
|
|
205
276
|
|
|
206
277
|
If **NO CHANGES** are required, respond with: \`{}\`
|
|
207
278
|
|
|
208
279
|
An empty object is the MOST COMMON expected response.
|
|
209
|
-
|
|
210
|
-
# Current Details of PERSON
|
|
211
|
-
|
|
212
|
-
${currentDetailsSection}
|
|
213
280
|
`;
|
|
214
281
|
|
|
282
|
+
// ── USER PROMPT ───────────────────────────────────────────────────────────
|
|
283
|
+
|
|
215
284
|
const earlierSection =
|
|
216
285
|
data.messages_context.length > 0
|
|
217
286
|
? `## Earlier Conversation
|
|
@@ -223,19 +292,25 @@ ${formatMessagesAsPlaceholders(data.messages_context, personaName)}
|
|
|
223
292
|
const recentSection = `## Most Recent Messages
|
|
224
293
|
${formatMessagesAsPlaceholders(data.messages_analyze, personaName)}`;
|
|
225
294
|
|
|
295
|
+
const personReminder = personName !== 'Unknown'
|
|
296
|
+
? `Remember: You are analyzing the record for **${personName}** (${personRelationship}). Focus only on information about this specific person.`
|
|
297
|
+
: `Remember: You are analyzing the record for the HUMAN USER's **${personRelationship}**. Focus only on information about this specific person.`;
|
|
298
|
+
|
|
226
299
|
const user = `# Conversation
|
|
227
300
|
${earlierSection}${recentSection}
|
|
228
301
|
|
|
229
302
|
---
|
|
230
303
|
|
|
231
|
-
|
|
304
|
+
${personReminder}
|
|
305
|
+
|
|
306
|
+
Analyze the Most Recent Messages and ${isNewItem ? 'create the PERSON record' : 'update the PERSON record'} if warranted.
|
|
232
307
|
|
|
233
308
|
**Return JSON:**
|
|
234
309
|
\`\`\`json
|
|
235
310
|
${jsonTemplate}
|
|
236
311
|
\`\`\`
|
|
237
312
|
|
|
238
|
-
If no changes are needed, respond with: \`{}\``;
|
|
313
|
+
If no ${isNewItem ? 'information found' : 'changes are needed'}, respond with: \`{}\``;
|
|
239
314
|
|
|
240
315
|
return { system, user };
|
|
241
316
|
}
|
|
@@ -250,7 +250,7 @@ ONLY ANALYZE the "Most Recent Messages". The "Earlier Conversation" is provided
|
|
|
250
250
|
${jsonTemplate}
|
|
251
251
|
\`\`\`
|
|
252
252
|
|
|
253
|
-
When returning a record,
|
|
253
|
+
When returning a record, always include \`sentiment\`. Include \`name\` only if you are changing it; omit it to keep the existing name. Always include \`description\` when returning a record.
|
|
254
254
|
|
|
255
255
|
If you find **NO EVIDENCE** of this TOPIC in the "Most Recent Messages", respond with: \`{}\`
|
|
256
256
|
|
|
@@ -26,7 +26,10 @@ export interface TopicScanPromptData extends BaseScanPromptData {
|
|
|
26
26
|
participant_context?: ParticipantContext;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export interface PersonScanPromptData extends BaseScanPromptData {
|
|
29
|
+
export interface PersonScanPromptData extends BaseScanPromptData {
|
|
30
|
+
participant_context?: ParticipantContext;
|
|
31
|
+
known_identifier_types?: string[];
|
|
32
|
+
}
|
|
30
33
|
|
|
31
34
|
export interface FactFindPromptData {
|
|
32
35
|
persona_name: string;
|
|
@@ -50,6 +53,7 @@ export interface TopicScanCandidate {
|
|
|
50
53
|
|
|
51
54
|
export interface PersonScanCandidate {
|
|
52
55
|
name: string;
|
|
56
|
+
identifiers?: Array<{ type: string; value: string; is_primary?: boolean }>;
|
|
53
57
|
description: string;
|
|
54
58
|
relationship: string;
|
|
55
59
|
reason: string;
|
package/src/prompts/index.ts
CHANGED
|
@@ -28,19 +28,12 @@ export type {
|
|
|
28
28
|
|
|
29
29
|
export {
|
|
30
30
|
buildPersonaTraitExtractionPrompt,
|
|
31
|
-
|
|
32
|
-
buildPersonaTopicMatchPrompt,
|
|
33
|
-
buildPersonaTopicUpdatePrompt,
|
|
31
|
+
buildPersonaTopicRatingPrompt,
|
|
34
32
|
} from "./persona/index.js";
|
|
35
33
|
export type {
|
|
36
34
|
PersonaTraitExtractionPromptData,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
PersonaTopicScanResult,
|
|
40
|
-
PersonaTopicMatchPromptData,
|
|
41
|
-
PersonaTopicMatchResult,
|
|
42
|
-
PersonaTopicUpdatePromptData,
|
|
43
|
-
PersonaTopicUpdateResult,
|
|
35
|
+
PersonaTopicRatingPromptData,
|
|
36
|
+
PersonaTopicRatingResult,
|
|
44
37
|
TraitResult,
|
|
45
38
|
} from "./persona/types.js";
|
|
46
39
|
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import type { Message } from "../core/types.js";
|
|
2
|
+
import { getMessageContent } from "../core/handlers/utils.js";
|
|
2
3
|
|
|
3
4
|
const MESSAGE_PLACEHOLDER_REGEX = /\[mid:([a-zA-Z0-9_-]+):([^\]]+)\]/g;
|
|
4
5
|
|
|
5
|
-
/**
|
|
6
|
-
* Returns the display text for a message from its structured fields.
|
|
7
|
-
* - action_response as _italics_
|
|
8
|
-
* - verbal_response as plain text
|
|
9
|
-
* - silence_reason shown so the user understands why a persona stayed silent
|
|
10
|
-
*/
|
|
11
6
|
export function getMessageDisplayText(message: Message): string | null {
|
|
12
7
|
const parts: string[] = [];
|
|
13
|
-
|
|
14
|
-
if (
|
|
8
|
+
const content = getMessageContent(message);
|
|
9
|
+
if (content) parts.push(content);
|
|
15
10
|
if (message.silence_reason) {
|
|
16
11
|
const name = message.speaker_name ?? 'Persona';
|
|
17
12
|
parts.push(`[${name} chose not to respond because: ${message.silence_reason}]`);
|
|
@@ -20,23 +15,14 @@ export function getMessageDisplayText(message: Message): string | null {
|
|
|
20
15
|
return parts.join('\n\n');
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
/**
|
|
24
|
-
* Builds the content string for a ChatMessage sent to the LLM.
|
|
25
|
-
* Unlike getMessageDisplayText (which is for frontend rendering and skips silence),
|
|
26
|
-
* this includes ALL structured fields so the persona has full conversational context:
|
|
27
|
-
* - action_response as _italics_
|
|
28
|
-
* - verbal_response as plain text
|
|
29
|
-
* - silence_reason as "You chose not to respond because: ..."
|
|
30
|
-
*/
|
|
31
18
|
export function buildChatMessageContent(message: Message): string {
|
|
32
19
|
const parts: string[] = [];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
parts.push(message.verbal_response);
|
|
20
|
+
const content = getMessageContent(message);
|
|
21
|
+
|
|
22
|
+
if (message._synthesis && content) {
|
|
23
|
+
parts.push(`[The user used your conversation to generate an image. The full prompt was: "${content}"]`);
|
|
24
|
+
} else if (content) {
|
|
25
|
+
parts.push(content);
|
|
40
26
|
}
|
|
41
27
|
if (message.silence_reason) {
|
|
42
28
|
parts.push(`You chose not to respond because: ${message.silence_reason}`);
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
export { buildPersonaTraitExtractionPrompt } from "./traits.js";
|
|
2
|
-
export {
|
|
3
|
-
export { buildPersonaTopicMatchPrompt } from "./topics-match.js";
|
|
4
|
-
export { buildPersonaTopicUpdatePrompt } from "./topics-update.js";
|
|
2
|
+
export { buildPersonaTopicRatingPrompt } from "./topics-rate.js";
|
|
5
3
|
export type {
|
|
6
4
|
PersonaTraitExtractionPromptData,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
PersonaTopicScanResult,
|
|
10
|
-
PersonaTopicMatchPromptData,
|
|
11
|
-
PersonaTopicMatchResult,
|
|
12
|
-
PersonaTopicUpdatePromptData,
|
|
13
|
-
PersonaTopicUpdateResult,
|
|
5
|
+
PersonaTopicRatingPromptData,
|
|
6
|
+
PersonaTopicRatingResult,
|
|
14
7
|
TraitResult,
|
|
15
8
|
PromptOutput,
|
|
16
9
|
} from "./types.js";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { PersonaTopicRatingPromptData, PromptOutput } from "./types.js";
|
|
2
|
+
import { formatMessagesAsPlaceholders } from "../message-utils.js";
|
|
3
|
+
|
|
4
|
+
export function buildPersonaTopicRatingPrompt(data: PersonaTopicRatingPromptData): PromptOutput {
|
|
5
|
+
if (!data.persona_name) {
|
|
6
|
+
throw new Error("buildPersonaTopicRatingPrompt: persona_name is required");
|
|
7
|
+
}
|
|
8
|
+
if (!data.topics || data.topics.length === 0) {
|
|
9
|
+
throw new Error("buildPersonaTopicRatingPrompt: topics array is required and must not be empty");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const personaName = data.persona_name;
|
|
13
|
+
|
|
14
|
+
const topicList = data.topics
|
|
15
|
+
.map(t => `- **${t.name}**: ${t.description_hint}`)
|
|
16
|
+
.join("\n");
|
|
17
|
+
|
|
18
|
+
const system = `# Task
|
|
19
|
+
|
|
20
|
+
You are rating how much each of ${personaName}'s topics was discussed in recent messages.
|
|
21
|
+
|
|
22
|
+
Your ONLY job is to rate exposure for the provided topics. Do NOT invent new topics. Do NOT analyze deeply. Just rate what you observe.
|
|
23
|
+
|
|
24
|
+
# ${personaName}'s Topics
|
|
25
|
+
|
|
26
|
+
${topicList}
|
|
27
|
+
|
|
28
|
+
# Rating Scale
|
|
29
|
+
|
|
30
|
+
For each topic, rate how much it was discussed in the "Most Recent Messages":
|
|
31
|
+
|
|
32
|
+
- **none**: Topic not mentioned or only trivial reference
|
|
33
|
+
- **low**: Mentioned briefly (1-2 messages, passing reference)
|
|
34
|
+
- **medium**: Discussed with some depth (3-5 messages or sustained engagement)
|
|
35
|
+
- **high**: Major focus of conversation (6+ messages or intense discussion)
|
|
36
|
+
|
|
37
|
+
# Critical Rules
|
|
38
|
+
|
|
39
|
+
1. **ONLY rate what's actually in the messages**. Do not infer or imagine.
|
|
40
|
+
2. **"none" is the expected answer for most topics most days**. Only rate higher if there's clear evidence.
|
|
41
|
+
3. **Rate based on the Most Recent Messages section ONLY**. Earlier conversation is context.
|
|
42
|
+
4. **Do NOT invent new topics**. Only rate the topics listed above.
|
|
43
|
+
|
|
44
|
+
# Response Format
|
|
45
|
+
|
|
46
|
+
Return JSON with ratings for each topic:
|
|
47
|
+
|
|
48
|
+
\`\`\`json
|
|
49
|
+
{
|
|
50
|
+
"ratings": [
|
|
51
|
+
{
|
|
52
|
+
"topic_id": "uuid-here",
|
|
53
|
+
"exposure_impact": "none"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"topic_id": "another-uuid",
|
|
57
|
+
"exposure_impact": "medium"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
**Return JSON only.**`;
|
|
64
|
+
|
|
65
|
+
const earlierSection = data.messages_context.length > 0
|
|
66
|
+
? `## Earlier Conversation (context only)
|
|
67
|
+
${formatMessagesAsPlaceholders(data.messages_context, personaName)}
|
|
68
|
+
|
|
69
|
+
`
|
|
70
|
+
: '';
|
|
71
|
+
|
|
72
|
+
const recentSection = `## Most Recent Messages (rate exposure based on these)
|
|
73
|
+
${formatMessagesAsPlaceholders(data.messages_analyze, personaName)}`;
|
|
74
|
+
|
|
75
|
+
const user = `# Conversation
|
|
76
|
+
${earlierSection}${recentSection}
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
Rate how much each of ${personaName}'s topics was discussed in the "Most Recent Messages".
|
|
81
|
+
|
|
82
|
+
**Return JSON:**
|
|
83
|
+
\`\`\`json
|
|
84
|
+
{
|
|
85
|
+
"ratings": [
|
|
86
|
+
{
|
|
87
|
+
"topic_id": "topic-uuid",
|
|
88
|
+
"exposure_impact": "none" | "low" | "medium" | "high"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
\`\`\``;
|
|
93
|
+
|
|
94
|
+
return { system, user };
|
|
95
|
+
}
|