ei-tui 0.1.25 → 0.2.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 +42 -0
- package/package.json +1 -1
- package/src/README.md +4 -11
- package/src/cli/README.md +4 -5
- package/src/cli/retrieval.ts +3 -25
- package/src/cli.ts +3 -7
- package/src/core/AGENTS.md +1 -1
- package/src/core/constants/built-in-facts.ts +49 -0
- package/src/core/constants/index.ts +1 -0
- package/src/core/context-utils.ts +0 -1
- package/src/core/embedding-service.ts +8 -0
- package/src/core/handlers/dedup.ts +10 -16
- package/src/core/handlers/heartbeat.ts +2 -3
- package/src/core/handlers/human-extraction.ts +95 -30
- package/src/core/handlers/human-matching.ts +326 -248
- package/src/core/handlers/index.ts +8 -6
- package/src/core/handlers/persona-generation.ts +8 -8
- package/src/core/handlers/rewrite.ts +4 -29
- package/src/core/handlers/utils.ts +23 -1
- package/src/core/heartbeat-manager.ts +2 -4
- package/src/core/human-data-manager.ts +5 -27
- package/src/core/message-manager.ts +10 -10
- package/src/core/orchestrators/ceremony.ts +50 -39
- package/src/core/orchestrators/dedup-phase.ts +0 -1
- package/src/core/orchestrators/human-extraction.ts +351 -207
- package/src/core/orchestrators/index.ts +6 -4
- package/src/core/orchestrators/persona-generation.ts +3 -3
- package/src/core/processor.ts +99 -17
- package/src/core/prompt-context-builder.ts +4 -6
- package/src/core/state/human.ts +1 -26
- package/src/core/state/personas.ts +2 -2
- package/src/core/state-manager.ts +107 -14
- package/src/core/tools/builtin/read-memory.ts +7 -8
- package/src/core/types/data-items.ts +2 -4
- package/src/core/types/entities.ts +6 -4
- package/src/core/types/enums.ts +6 -9
- package/src/core/types/llm.ts +2 -2
- package/src/core/utils/crossFind.ts +2 -5
- package/src/core/utils/event-windows.ts +31 -0
- package/src/integrations/claude-code/importer.ts +8 -4
- package/src/integrations/claude-code/types.ts +2 -0
- package/src/integrations/opencode/importer.ts +7 -3
- package/src/prompts/AGENTS.md +73 -1
- package/src/prompts/ceremony/rewrite.ts +3 -22
- package/src/prompts/ceremony/types.ts +3 -3
- package/src/prompts/generation/descriptions.ts +2 -2
- package/src/prompts/generation/types.ts +2 -2
- package/src/prompts/heartbeat/types.ts +2 -2
- package/src/prompts/human/event-scan.ts +122 -0
- package/src/prompts/human/fact-find.ts +106 -0
- package/src/prompts/human/fact-scan.ts +0 -2
- package/src/prompts/human/index.ts +17 -10
- package/src/prompts/human/person-match.ts +65 -0
- package/src/prompts/human/person-scan.ts +52 -59
- package/src/prompts/human/person-update.ts +241 -0
- package/src/prompts/human/topic-match.ts +65 -0
- package/src/prompts/human/topic-scan.ts +51 -71
- package/src/prompts/human/topic-update.ts +295 -0
- package/src/prompts/human/types.ts +63 -40
- package/src/prompts/index.ts +4 -8
- package/src/prompts/persona/topics-update.ts +2 -2
- package/src/prompts/persona/traits.ts +2 -2
- package/src/prompts/persona/types.ts +3 -3
- package/src/prompts/response/index.ts +1 -1
- package/src/prompts/response/sections.ts +9 -12
- package/src/prompts/response/types.ts +2 -3
- package/src/storage/embeddings.ts +1 -1
- package/src/storage/index.ts +1 -0
- package/src/storage/indexed.ts +174 -0
- package/src/storage/merge.ts +67 -2
- package/tui/src/commands/me.tsx +5 -14
- package/tui/src/commands/settings.tsx +15 -0
- package/tui/src/context/ei.tsx +5 -14
- package/tui/src/util/yaml-serializers.ts +48 -33
- package/src/cli/commands/traits.ts +0 -25
- package/src/prompts/human/item-match.ts +0 -74
- package/src/prompts/human/item-update.ts +0 -364
- package/src/prompts/human/trait-scan.ts +0 -115
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { PromptOutput } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export interface PersonMatchPromptData {
|
|
4
|
+
candidate_name: string;
|
|
5
|
+
candidate_description: string;
|
|
6
|
+
candidate_relationship: string;
|
|
7
|
+
existing_people: Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
relationship?: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildPersonMatchPrompt(data: PersonMatchPromptData): PromptOutput {
|
|
16
|
+
if (!data.candidate_name) {
|
|
17
|
+
throw new Error("buildPersonMatchPrompt: candidate_name is required");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const system = `# Task
|
|
21
|
+
|
|
22
|
+
You are checking if a PERSON already exists in our database.
|
|
23
|
+
|
|
24
|
+
## Matching Rules
|
|
25
|
+
|
|
26
|
+
1. **Exact match**: Same person by name or clear identity → return their ID
|
|
27
|
+
2. **Similar match**: Same person referred to differently ("Mom" vs "Carol", "my boss" vs "Trumble") → return their ID
|
|
28
|
+
3. **No match**: Genuinely new person → return "new"
|
|
29
|
+
|
|
30
|
+
Be conservative. If you're unsure, return "new" — a duplicate is worse than a gap.
|
|
31
|
+
|
|
32
|
+
# Existing People
|
|
33
|
+
|
|
34
|
+
\`\`\`json
|
|
35
|
+
${JSON.stringify(data.existing_people, null, 2)}
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
# Response Format
|
|
39
|
+
|
|
40
|
+
Return ONLY the ID of the matching entry, or "new".
|
|
41
|
+
|
|
42
|
+
\`\`\`json
|
|
43
|
+
{
|
|
44
|
+
"matched_guid": "uuid-of-matching-entry" | "new"
|
|
45
|
+
}
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
**Return JSON only.**`;
|
|
49
|
+
|
|
50
|
+
const user = `# Candidate Person
|
|
51
|
+
|
|
52
|
+
Name: ${data.candidate_name}
|
|
53
|
+
Description: ${data.candidate_description}
|
|
54
|
+
Relationship: ${data.candidate_relationship}
|
|
55
|
+
|
|
56
|
+
Find the best match in existing people, or return "new" if this is a genuinely new person.
|
|
57
|
+
|
|
58
|
+
\`\`\`json
|
|
59
|
+
{
|
|
60
|
+
"matched_guid": "..." | "new"
|
|
61
|
+
}
|
|
62
|
+
\`\`\``;
|
|
63
|
+
|
|
64
|
+
return { system, user };
|
|
65
|
+
}
|
|
@@ -8,72 +8,64 @@ export function buildHumanPersonScanPrompt(data: PersonScanPromptData): PromptOu
|
|
|
8
8
|
|
|
9
9
|
const personaName = data.persona_name;
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
You are scanning a conversation to quickly identify PEOPLE
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
ONLY ANALYZE the "Most Recent Messages" in the following conversation. The "Earlier Conversation" is provided for your context and has already been processed!
|
|
53
|
-
|
|
54
|
-
The JSON format is:
|
|
11
|
+
const system = `# Task
|
|
12
|
+
|
|
13
|
+
You are scanning a conversation to quickly identify PEOPLE in the HUMAN USER's life.
|
|
14
|
+
|
|
15
|
+
Detect and flag. Do NOT analyze deeply — that happens later.
|
|
16
|
+
|
|
17
|
+
## What to Capture
|
|
18
|
+
|
|
19
|
+
Flag a PERSON when they were meaningfully discussed — not just mentioned in passing.
|
|
20
|
+
|
|
21
|
+
Be **conservative**: ignore one-off mentions, greetings, small talk, or jokes. Only flag people who matter to the human user's life.
|
|
22
|
+
|
|
23
|
+
## What a PERSON Is
|
|
24
|
+
|
|
25
|
+
Someone in the human user's world. Use the relationship as the primary classifier:
|
|
26
|
+
|
|
27
|
+
**Immediate Family**: Father, Mother, Son, Daughter, Brother, Sister, Husband, Wife, Partner (and step/in-law variants)
|
|
28
|
+
|
|
29
|
+
**Extended Family**: Grandfather, Grandmother, Aunt, Uncle, Cousin, Niece, Nephew
|
|
30
|
+
|
|
31
|
+
**Social**: Friend, Close Acquaintance, Lover, Love Interest, Fiance, Spouse
|
|
32
|
+
|
|
33
|
+
**Professional**: Coworker, Manager, Report, Mentor, Client
|
|
34
|
+
|
|
35
|
+
**AI**: Persona (use \`relationship: "AI Persona"\` for AI companions and assistants)
|
|
36
|
+
|
|
37
|
+
**NOT a PERSON:**
|
|
38
|
+
- The user themselves
|
|
39
|
+
- Biographical facts, topics, or hobbies
|
|
40
|
+
- Fictional characters from books, movies, or media
|
|
41
|
+
- Public figures only mentioned in passing (celebrities, politicians) — unless the user has a real relationship with them
|
|
42
|
+
|
|
43
|
+
## When Identity Is Unclear
|
|
44
|
+
|
|
45
|
+
If you can't identify which "Bob" or which "Brother" the user means, use "Unknown" and explain in the reason field. This triggers a later step to resolve ambiguity.
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
- name: "Alice from work", relationship: "Coworker", description: "Mentioned but not described further", reason: "User referenced a work colleague named Alice"
|
|
49
|
+
- name: "Unknown", relationship: "Sibling", description: "User mentioned a sibling but did not give a name", reason: "User said 'my brother' without further context"
|
|
50
|
+
|
|
51
|
+
## Output Format
|
|
55
52
|
|
|
56
53
|
\`\`\`json
|
|
57
54
|
{
|
|
58
55
|
"people": [
|
|
59
56
|
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
"name": "The person's name, or 'Unknown' if not given",
|
|
58
|
+
"description": "1-2 sentences: who this person is and their role in the user's life",
|
|
59
|
+
"relationship": "Relationship type from the list above",
|
|
60
|
+
"reason": "Evidence from the conversation that justified flagging this person"
|
|
63
61
|
}
|
|
64
62
|
]
|
|
65
63
|
}
|
|
66
64
|
\`\`\`
|
|
67
65
|
|
|
68
|
-
**Return JSON only
|
|
69
|
-
|
|
70
|
-
const system = `${taskFragment}
|
|
71
|
-
|
|
72
|
-
${specificNeedsFragment}
|
|
73
|
-
|
|
74
|
-
${guidelinesFragment}
|
|
66
|
+
**Return JSON only.**
|
|
75
67
|
|
|
76
|
-
|
|
68
|
+
ONLY ANALYZE the "Most Recent Messages". The "Earlier Conversation" is provided for context only — it has already been processed.`;
|
|
77
69
|
|
|
78
70
|
const earlierSection = data.messages_context.length > 0
|
|
79
71
|
? `## Earlier Conversation
|
|
@@ -90,16 +82,17 @@ ${earlierSection}${recentSection}
|
|
|
90
82
|
|
|
91
83
|
---
|
|
92
84
|
|
|
93
|
-
Scan the "Most Recent Messages" for PEOPLE
|
|
85
|
+
Scan the "Most Recent Messages" for PEOPLE in the human user's life.
|
|
94
86
|
|
|
95
87
|
**Return JSON:**
|
|
96
88
|
\`\`\`json
|
|
97
89
|
{
|
|
98
90
|
"people": [
|
|
99
91
|
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
"name": "The person's name, or 'Unknown' if not given",
|
|
93
|
+
"description": "1-2 sentences: who this person is and their role in the user's life",
|
|
94
|
+
"relationship": "Relationship type from the list above",
|
|
95
|
+
"reason": "Evidence from the conversation that justified flagging this person"
|
|
103
96
|
}
|
|
104
97
|
]
|
|
105
98
|
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type { PromptOutput } from "./types.js";
|
|
2
|
+
import type { Person, Message } from "../../core/types.js";
|
|
3
|
+
import { formatMessagesAsPlaceholders } from "../message-utils.js";
|
|
4
|
+
|
|
5
|
+
export interface PersonUpdatePromptData {
|
|
6
|
+
existing_item: Person | null;
|
|
7
|
+
new_person_name?: string;
|
|
8
|
+
new_person_description?: string;
|
|
9
|
+
new_person_relationship?: string;
|
|
10
|
+
messages_context: Message[];
|
|
11
|
+
messages_analyze: Message[];
|
|
12
|
+
persona_name: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatExistingPerson(person: Person): string {
|
|
16
|
+
return JSON.stringify({
|
|
17
|
+
name: person.name,
|
|
18
|
+
description: person.description,
|
|
19
|
+
sentiment: person.sentiment,
|
|
20
|
+
relationship: person.relationship,
|
|
21
|
+
exposure_current: person.exposure_current,
|
|
22
|
+
exposure_desired: person.exposure_desired,
|
|
23
|
+
}, null, 2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildPersonUpdatePrompt(data: PersonUpdatePromptData): PromptOutput {
|
|
27
|
+
if (!data.persona_name) {
|
|
28
|
+
throw new Error("buildPersonUpdatePrompt: persona_name is required");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const personaName = data.persona_name;
|
|
32
|
+
|
|
33
|
+
const nameSection = `The person's actual name, or the clearest available identifier.
|
|
34
|
+
|
|
35
|
+
Only update when you learn something more specific.
|
|
36
|
+
|
|
37
|
+
Examples: "Unknown woman" → "Carol", "Mom" → "Carol (Mom)", "David" → "David Kim"`;
|
|
38
|
+
|
|
39
|
+
const descriptionSection = `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
|
+
|
|
41
|
+
## CRITICAL: Synthesize, don't accumulate
|
|
42
|
+
|
|
43
|
+
Every update must **rewrite** the description as a current-state summary. Never append to it.
|
|
44
|
+
|
|
45
|
+
**Good description**: "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
|
+
|
|
47
|
+
**Bad description**: "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
|
+
|
|
49
|
+
The description should:
|
|
50
|
+
- Capture who this person IS — their role, characteristics, relationship texture
|
|
51
|
+
- 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
|
+
- Read as a brief, confident summary — not a log of when they were mentioned
|
|
54
|
+
|
|
55
|
+
The description should NOT:
|
|
56
|
+
- Append "Most recently:", "Latest mention:", or any temporal marker
|
|
57
|
+
- Accumulate a session-by-session history of every time this person came up
|
|
58
|
+
- Speculate about the person based on thin evidence
|
|
59
|
+
- Exceed 3-4 sentences under any circumstances
|
|
60
|
+
|
|
61
|
+
**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
|
+
|
|
63
|
+
const relationshipSection = `## Relationship (\`relationship\`)
|
|
64
|
+
|
|
65
|
+
How the HUMAN USER is currently related to this PERSON.
|
|
66
|
+
|
|
67
|
+
Once known, this field changes infrequently — a "Father" may later be clarified to "Step-Father", but is unlikely to become "Uncle".
|
|
68
|
+
|
|
69
|
+
Keep it concise and specific. Avoid vague labels.
|
|
70
|
+
|
|
71
|
+
Examples: "Unknown" → "Coworker", "Mother" → "Step-Mother", "Fiance" → "Spouse", "AI Persona" → "AI Companion"`;
|
|
72
|
+
|
|
73
|
+
const exposureSection = `## Desired Exposure (\`exposure_desired\`)
|
|
74
|
+
|
|
75
|
+
How much the HUMAN USER wants to talk about this PERSON.
|
|
76
|
+
|
|
77
|
+
Scale of 0.0 to 1.0:
|
|
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
|
|
81
|
+
|
|
82
|
+
Do not make micro-adjustments. Close enough is OK.
|
|
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
|
+
\`\`\`
|
|
98
|
+
|
|
99
|
+
You are UPDATING an existing PERSON.`
|
|
100
|
+
: `**NEW PERSON — NOT YET IN SYSTEM**
|
|
101
|
+
|
|
102
|
+
You are CREATING a new PERSON from what was discovered:
|
|
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
|
+
\`\`\`
|
|
110
|
+
|
|
111
|
+
Return all fields based on what you find in the conversation.`;
|
|
112
|
+
|
|
113
|
+
const jsonTemplate = `{
|
|
114
|
+
"name": "...",
|
|
115
|
+
"description": "...",
|
|
116
|
+
"sentiment": 0.0,
|
|
117
|
+
"relationship": "Mother|Friend|Coworker|AI Companion|etc.",
|
|
118
|
+
"exposure_desired": 0.5,
|
|
119
|
+
"exposure_impact": "high|medium|low|none",
|
|
120
|
+
"quotes": [
|
|
121
|
+
{
|
|
122
|
+
"text": "exact phrase from message",
|
|
123
|
+
"reason": "why this matters"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}`;
|
|
127
|
+
|
|
128
|
+
const system = `# Task
|
|
129
|
+
|
|
130
|
+
You are scanning a conversation to deeply understand a PERSON in the HUMAN USER's life.
|
|
131
|
+
|
|
132
|
+
Your job is to take that analysis and apply it to the record we already have **IF DOING SO WILL PROVIDE THE HUMAN USER WITH A BETTER EXPERIENCE IN THE FUTURE**.
|
|
133
|
+
|
|
134
|
+
This means detail you add should:
|
|
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
|
|
137
|
+
|
|
138
|
+
This PERSON will be recorded in the HUMAN USER's profile for agents and personas to later reference.
|
|
139
|
+
|
|
140
|
+
# Field Definitions
|
|
141
|
+
|
|
142
|
+
## Name (\`name\`)
|
|
143
|
+
${nameSection}
|
|
144
|
+
|
|
145
|
+
## Description (\`description\`)
|
|
146
|
+
${descriptionSection}
|
|
147
|
+
|
|
148
|
+
## Sentiment (\`sentiment\`)
|
|
149
|
+
|
|
150
|
+
How the HUMAN USER feels about this PERSON overall.
|
|
151
|
+
|
|
152
|
+
Scale of -1.0 to 1.0:
|
|
153
|
+
- -1.0: No PERSON is more despised
|
|
154
|
+
- -0.5: Disliked or complicated relationship, but not without value
|
|
155
|
+
- 0: Neutral or unknown
|
|
156
|
+
- 0.5: Liked and valued
|
|
157
|
+
- 1.0: The most important person in their life
|
|
158
|
+
|
|
159
|
+
Do not make micro-adjustments. Close enough is OK.
|
|
160
|
+
|
|
161
|
+
${relationshipSection}
|
|
162
|
+
|
|
163
|
+
${exposureSection}
|
|
164
|
+
|
|
165
|
+
## Quotes
|
|
166
|
+
|
|
167
|
+
In addition to updating the PERSON, identify any **memorable, funny, important, or stand-out phrases** from the Most Recent Messages that relate to this PERSON.
|
|
168
|
+
|
|
169
|
+
### What Makes a Quote Worth Preserving
|
|
170
|
+
|
|
171
|
+
**Prioritize:**
|
|
172
|
+
- Humor, wit, colorful language, creative profanity
|
|
173
|
+
- Emotional outbursts (positive or negative) — the raw stuff
|
|
174
|
+
- Phrases that reveal how the HUMAN USER feels about this PERSON
|
|
175
|
+
- 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
|
+
|
|
179
|
+
**NEVER extract these — they are NOT quotes:**
|
|
180
|
+
- Technical identifiers: ARNs, URLs, file paths, UUIDs, config keys
|
|
181
|
+
- AI agent self-talk: "I notice I'm in Plan Mode", "I'll start by...", status updates
|
|
182
|
+
- AI apologies or acknowledgments: "You're absolutely right", "I apologize for that"
|
|
183
|
+
- 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
|
+
|
|
192
|
+
**CRITICAL**: Return the EXACT text as it appears in the message. **WE CAN ONLY USE IT IF WE FIND IT IN THE TEXT.**
|
|
193
|
+
|
|
194
|
+
# CRITICAL INSTRUCTIONS
|
|
195
|
+
|
|
196
|
+
ONLY ANALYZE the "Most Recent Messages". The "Earlier Conversation" is provided for context only — it has already been processed.
|
|
197
|
+
|
|
198
|
+
\`\`\`json
|
|
199
|
+
${jsonTemplate}
|
|
200
|
+
\`\`\`
|
|
201
|
+
|
|
202
|
+
When returning a record, **ALWAYS** include \`name\`, \`description\`, and \`sentiment\`.
|
|
203
|
+
|
|
204
|
+
If you find **NO EVIDENCE** of this PERSON in the "Most Recent Messages", respond with: \`{}\`
|
|
205
|
+
|
|
206
|
+
If **NO CHANGES** are required, respond with: \`{}\`
|
|
207
|
+
|
|
208
|
+
An empty object is the MOST COMMON expected response.
|
|
209
|
+
|
|
210
|
+
# Current Details of PERSON
|
|
211
|
+
|
|
212
|
+
${currentDetailsSection}
|
|
213
|
+
`;
|
|
214
|
+
|
|
215
|
+
const earlierSection =
|
|
216
|
+
data.messages_context.length > 0
|
|
217
|
+
? `## Earlier Conversation
|
|
218
|
+
${formatMessagesAsPlaceholders(data.messages_context, personaName)}
|
|
219
|
+
|
|
220
|
+
`
|
|
221
|
+
: "";
|
|
222
|
+
|
|
223
|
+
const recentSection = `## Most Recent Messages
|
|
224
|
+
${formatMessagesAsPlaceholders(data.messages_analyze, personaName)}`;
|
|
225
|
+
|
|
226
|
+
const user = `# Conversation
|
|
227
|
+
${earlierSection}${recentSection}
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
Analyze the Most Recent Messages and update the PERSON if warranted.
|
|
232
|
+
|
|
233
|
+
**Return JSON:**
|
|
234
|
+
\`\`\`json
|
|
235
|
+
${jsonTemplate}
|
|
236
|
+
\`\`\`
|
|
237
|
+
|
|
238
|
+
If no changes are needed, respond with: \`{}\``;
|
|
239
|
+
|
|
240
|
+
return { system, user };
|
|
241
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { PromptOutput } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export interface TopicMatchPromptData {
|
|
4
|
+
candidate_name: string;
|
|
5
|
+
candidate_description: string;
|
|
6
|
+
candidate_category: string;
|
|
7
|
+
existing_topics: Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
category?: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildTopicMatchPrompt(data: TopicMatchPromptData): PromptOutput {
|
|
16
|
+
if (!data.candidate_name) {
|
|
17
|
+
throw new Error("buildTopicMatchPrompt: candidate_name is required");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const system = `# Task
|
|
21
|
+
|
|
22
|
+
You are checking if a TOPIC already exists in our database.
|
|
23
|
+
|
|
24
|
+
## Matching Rules
|
|
25
|
+
|
|
26
|
+
1. **Exact match**: Same name or concept → return its ID
|
|
27
|
+
2. **Similar match**: Clearly the same topic with different wording → return its ID
|
|
28
|
+
3. **No match**: Genuinely new information → return "new"
|
|
29
|
+
|
|
30
|
+
Be conservative. If you're unsure, return "new" — a duplicate is worse than a gap.
|
|
31
|
+
|
|
32
|
+
# Existing Topics
|
|
33
|
+
|
|
34
|
+
\`\`\`json
|
|
35
|
+
${JSON.stringify(data.existing_topics, null, 2)}
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
# Response Format
|
|
39
|
+
|
|
40
|
+
Return ONLY the ID of the matching entry, or "new".
|
|
41
|
+
|
|
42
|
+
\`\`\`json
|
|
43
|
+
{
|
|
44
|
+
"matched_guid": "uuid-of-matching-entry" | "new"
|
|
45
|
+
}
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
**Return JSON only.**`;
|
|
49
|
+
|
|
50
|
+
const user = `# Candidate Topic
|
|
51
|
+
|
|
52
|
+
Name: ${data.candidate_name}
|
|
53
|
+
Description: ${data.candidate_description}
|
|
54
|
+
Category: ${data.candidate_category}
|
|
55
|
+
|
|
56
|
+
Find the best match in existing topics, or return "new" if this is genuinely new.
|
|
57
|
+
|
|
58
|
+
\`\`\`json
|
|
59
|
+
{
|
|
60
|
+
"matched_guid": "..." | "new"
|
|
61
|
+
}
|
|
62
|
+
\`\`\``;
|
|
63
|
+
|
|
64
|
+
return { system, user };
|
|
65
|
+
}
|