ei-tui 0.1.25 → 0.3.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/README.md +42 -0
- package/package.json +2 -1
- package/src/README.md +4 -11
- package/src/cli/README.md +87 -7
- package/src/cli/commands/facts.ts +2 -2
- package/src/cli/commands/people.ts +2 -2
- package/src/cli/commands/quotes.ts +2 -2
- package/src/cli/commands/topics.ts +2 -2
- package/src/cli/mcp.ts +94 -0
- package/src/cli/retrieval.ts +67 -31
- package/src/cli.ts +64 -23
- 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 +11 -23
- package/src/core/handlers/heartbeat.ts +2 -3
- package/src/core/handlers/human-extraction.ts +96 -30
- package/src/core/handlers/human-matching.ts +328 -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 -51
- package/src/core/handlers/utils.ts +23 -1
- package/src/core/heartbeat-manager.ts +2 -4
- package/src/core/human-data-manager.ts +38 -36
- package/src/core/message-manager.ts +10 -10
- package/src/core/orchestrators/ceremony.ts +49 -44
- package/src/core/orchestrators/dedup-phase.ts +2 -4
- 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 +167 -20
- 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 +13 -18
- package/src/core/types/data-items.ts +3 -4
- package/src/core/types/entities.ts +7 -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 +14 -5
- package/src/integrations/claude-code/types.ts +3 -0
- package/src/integrations/cursor/importer.ts +282 -0
- package/src/integrations/cursor/index.ts +10 -0
- package/src/integrations/cursor/reader.ts +209 -0
- package/src/integrations/cursor/types.ts +140 -0
- package/src/integrations/opencode/importer.ts +14 -4
- package/src/prompts/AGENTS.md +73 -1
- package/src/prompts/ceremony/dedup.ts +0 -33
- package/src/prompts/ceremony/rewrite.ts +6 -41
- package/src/prompts/ceremony/types.ts +4 -4
- 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 +76 -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,295 @@
|
|
|
1
|
+
import type { PromptOutput, ParticipantContext } from "./types.js";
|
|
2
|
+
import type { Topic, Message } from "../../core/types.js";
|
|
3
|
+
import { formatMessagesAsPlaceholders } from "../message-utils.js";
|
|
4
|
+
|
|
5
|
+
function participantContextSection(ctx: ParticipantContext | undefined): string {
|
|
6
|
+
if (!ctx) return "";
|
|
7
|
+
const lines: string[] = ["# Participant Context", "The following may help you understand what themes and moments are meaningful in this conversation.", ""];
|
|
8
|
+
lines.push(`## Persona: ${ctx.persona_name}`);
|
|
9
|
+
if (ctx.persona_description) lines.push(ctx.persona_description);
|
|
10
|
+
lines.push("");
|
|
11
|
+
lines.push("## Human");
|
|
12
|
+
if (ctx.human_name) lines.push(`Name: ${ctx.human_name}`);
|
|
13
|
+
if (ctx.human_age !== undefined) lines.push(`Age: ${ctx.human_age}`);
|
|
14
|
+
lines.push("");
|
|
15
|
+
return lines.join("\n");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TopicUpdatePromptData {
|
|
19
|
+
existing_item: Topic | null;
|
|
20
|
+
new_topic_name?: string;
|
|
21
|
+
new_topic_description?: string;
|
|
22
|
+
new_topic_category?: string;
|
|
23
|
+
messages_context: Message[];
|
|
24
|
+
messages_analyze: Message[];
|
|
25
|
+
persona_name: string;
|
|
26
|
+
participant_context?: ParticipantContext;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatExistingTopic(topic: Topic): string {
|
|
30
|
+
return JSON.stringify({
|
|
31
|
+
name: topic.name,
|
|
32
|
+
description: topic.description,
|
|
33
|
+
sentiment: topic.sentiment,
|
|
34
|
+
category: topic.category,
|
|
35
|
+
exposure_current: topic.exposure_current,
|
|
36
|
+
exposure_desired: topic.exposure_desired,
|
|
37
|
+
}, null, 2);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function buildTopicUpdatePrompt(data: TopicUpdatePromptData): PromptOutput {
|
|
41
|
+
if (!data.persona_name) {
|
|
42
|
+
throw new Error("buildTopicUpdatePrompt: persona_name is required");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const personaName = data.persona_name;
|
|
46
|
+
const isEvent =
|
|
47
|
+
data.existing_item?.category === "Event" ||
|
|
48
|
+
data.new_topic_category === "Event";
|
|
49
|
+
|
|
50
|
+
const nameSection = `Should be a short, evocative label for the TOPIC.
|
|
51
|
+
|
|
52
|
+
Only update for clarification or further specificity.
|
|
53
|
+
|
|
54
|
+
Examples: "Unknown" → "Ei Platform Architecture", "Work stress" → "Job transition anxiety"`;
|
|
55
|
+
|
|
56
|
+
const descriptionSection = isEvent
|
|
57
|
+
? `A narrative account of a specific significant moment — written as a memory, not a summary.
|
|
58
|
+
|
|
59
|
+
## CRITICAL: Events are MOMENTS, not themes
|
|
60
|
+
|
|
61
|
+
An Event description captures a single bounded experience. It should read like "if you described this to someone who wasn't there."
|
|
62
|
+
|
|
63
|
+
**Good description**: "First session where Beta had read access to the Ei codebase (early March 2026). Spent the conversation exploring the project structure, then diagnosed the JSON recovery edge case. The debugging felt genuinely collaborative — the 'Crash Test Cutie' framing made sense for the first time."
|
|
64
|
+
|
|
65
|
+
**Bad description**: "Beta has filesystem access and regularly uses it to debug the Ei project. Ongoing collaboration continues."
|
|
66
|
+
|
|
67
|
+
The description should:
|
|
68
|
+
- Name the moment specifically (what happened, rough time if known)
|
|
69
|
+
- Capture what made it significant (what changed, what was felt, what it led to)
|
|
70
|
+
- Be specific enough to summon the memory, short enough to not be a recap
|
|
71
|
+
- Read as a story beat, not a state summary
|
|
72
|
+
|
|
73
|
+
The description should NOT:
|
|
74
|
+
- Track an ongoing relationship or theme (that's a regular TOPIC)
|
|
75
|
+
- Accumulate all conversations that touched this moment
|
|
76
|
+
- Read like a system log or changelog
|
|
77
|
+
|
|
78
|
+
**Style**: Write it the way a good friend would tell someone else about a memorable moment. Present tense is fine.`
|
|
79
|
+
: `A concise, evergreen summary of what is currently known about this TOPIC. Personas use this to recall context and make meaningful references.
|
|
80
|
+
|
|
81
|
+
## CRITICAL: Synthesize, don't accumulate
|
|
82
|
+
|
|
83
|
+
Every update must **rewrite** the description as a current-state summary. Never append to it.
|
|
84
|
+
|
|
85
|
+
**Good description**: "Active project to improve test coverage. Settled on Vitest + E2E harness. Currently focused on pipeline integration and extraction logic coverage."
|
|
86
|
+
|
|
87
|
+
**Bad description**: "User asked Sisyphus to create a ticket... Later: pruned overengineered framework... Most recent session: added PR checks..."
|
|
88
|
+
|
|
89
|
+
The description should:
|
|
90
|
+
- Capture what is true NOW — the current state, decisions made, where things stand
|
|
91
|
+
- Include details a persona would use to show genuine recall ("Oh right, you were working on the pipeline tests")
|
|
92
|
+
- Be useful to a persona meeting this human for the first time
|
|
93
|
+
- Read as a brief summary paragraph, not a session log
|
|
94
|
+
|
|
95
|
+
The description should NOT:
|
|
96
|
+
- Append "Most recent:", "Latest:", "Current session:", or any temporal marker
|
|
97
|
+
- Accumulate a running history of every conversation that touched this TOPIC
|
|
98
|
+
- Exceed 3-4 sentences under any circumstances
|
|
99
|
+
|
|
100
|
+
**ABSOLUTELY VITAL**: Do **NOT** embellish — personas use their own voice. Capture what is true, not a log of how you got here.`;
|
|
101
|
+
|
|
102
|
+
const categorySection = `## Category (\`category\`)
|
|
103
|
+
|
|
104
|
+
The type/category of this TOPIC. Pick the most appropriate:
|
|
105
|
+
- **Interest**: Hobbies, activities, ongoing fascinations
|
|
106
|
+
- **Goal**: Things they want to achieve
|
|
107
|
+
- **Dream**: Aspirational, maybe unrealistic desires
|
|
108
|
+
- **Conflict**: Internal struggles, dilemmas
|
|
109
|
+
- **Concern**: Worries, anxieties about something real
|
|
110
|
+
- **Fear**: Things that scare them
|
|
111
|
+
- **Hope**: Positive expectations for the future
|
|
112
|
+
- **Plan**: Concrete intentions with steps in mind
|
|
113
|
+
- **Project**: Active undertakings with real progress
|
|
114
|
+
- **Event**: A specific, significant moment that either party might reference later ("remember when...")
|
|
115
|
+
|
|
116
|
+
**Event vs. everything else**: An Event is bounded in time — it happened, it meant something, it's now a shared reference point. If you're describing an ongoing relationship or recurring theme, that's not an Event.
|
|
117
|
+
|
|
118
|
+
If the TOPIC is currently categorized as Event, keep it as Event unless you have strong evidence it should change.`;
|
|
119
|
+
|
|
120
|
+
const exposureSection = `## Desired Exposure (\`exposure_desired\`)
|
|
121
|
+
|
|
122
|
+
How much the HUMAN USER wants to talk about this TOPIC.
|
|
123
|
+
|
|
124
|
+
Scale of 0.0 to 1.0:
|
|
125
|
+
- 0.0: Never wants to hear about this TOPIC again
|
|
126
|
+
- 0.5: Average amount of engagement
|
|
127
|
+
- 1.0: This TOPIC is the sole focus of their existence
|
|
128
|
+
|
|
129
|
+
Do not make micro-adjustments. Close enough is OK.
|
|
130
|
+
|
|
131
|
+
## Exposure Impact (\`exposure_impact\`)
|
|
132
|
+
|
|
133
|
+
Not in the current data — but include it in your response.
|
|
134
|
+
|
|
135
|
+
How much this conversation should count toward exposure tracking:
|
|
136
|
+
- "high": Long, detailed conversation exclusively about this TOPIC
|
|
137
|
+
- "medium": Long OR detailed conversation about this TOPIC
|
|
138
|
+
- "low": The conversation touched on this TOPIC briefly
|
|
139
|
+
- "none": Only alluded to or hinted at`;
|
|
140
|
+
|
|
141
|
+
const currentDetailsSection = data.existing_item
|
|
142
|
+
? `\`\`\`json
|
|
143
|
+
${formatExistingTopic(data.existing_item)}
|
|
144
|
+
\`\`\`
|
|
145
|
+
|
|
146
|
+
You are UPDATING an existing TOPIC.`
|
|
147
|
+
: `**NEW TOPIC — NOT YET IN SYSTEM**
|
|
148
|
+
|
|
149
|
+
You are CREATING a new TOPIC from what was discovered:
|
|
150
|
+
\`\`\`json
|
|
151
|
+
{
|
|
152
|
+
"name": "${data.new_topic_name ?? "Unknown"}",
|
|
153
|
+
"description": "${data.new_topic_description ?? "Details unknown"}",
|
|
154
|
+
"category": "${data.new_topic_category ?? "Interest"}"
|
|
155
|
+
}
|
|
156
|
+
\`\`\`
|
|
157
|
+
|
|
158
|
+
Return all fields based on what you find in the conversation.`;
|
|
159
|
+
|
|
160
|
+
const jsonTemplate = `{
|
|
161
|
+
"name": "...",
|
|
162
|
+
"description": "...",
|
|
163
|
+
"sentiment": 0.0,
|
|
164
|
+
"category": "Interest|Goal|Dream|Conflict|Concern|Fear|Hope|Plan|Project|Event",
|
|
165
|
+
"exposure_desired": 0.5,
|
|
166
|
+
"exposure_impact": "high|medium|low|none",
|
|
167
|
+
"quotes": [
|
|
168
|
+
{
|
|
169
|
+
"text": "exact phrase from message",
|
|
170
|
+
"reason": "why this matters"
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}`;
|
|
174
|
+
|
|
175
|
+
const system = `# Task
|
|
176
|
+
|
|
177
|
+
You are scanning a conversation to deeply understand a TOPIC.
|
|
178
|
+
|
|
179
|
+
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**.
|
|
180
|
+
|
|
181
|
+
This means detail you add should:
|
|
182
|
+
1. Be meaningful, accurate, or still true to the HUMAN USER in six months or more
|
|
183
|
+
2. **NOT** already be present in the description or name of the TOPIC
|
|
184
|
+
|
|
185
|
+
This TOPIC will be recorded in the HUMAN USER's profile for agents and personas to later reference.
|
|
186
|
+
|
|
187
|
+
# Field Definitions
|
|
188
|
+
|
|
189
|
+
## Name (\`name\`)
|
|
190
|
+
${nameSection}
|
|
191
|
+
|
|
192
|
+
## Description (\`description\`)
|
|
193
|
+
${descriptionSection}
|
|
194
|
+
|
|
195
|
+
## Sentiment (\`sentiment\`)
|
|
196
|
+
|
|
197
|
+
How strongly the HUMAN USER feels about this TOPIC.
|
|
198
|
+
|
|
199
|
+
Scale of -1.0 to 1.0:
|
|
200
|
+
- -1.0: No TOPIC is more hated
|
|
201
|
+
- -0.5: Disliked, but some redeeming qualities
|
|
202
|
+
- 0: Neutral
|
|
203
|
+
- 0.5: Enjoyed, but recognizes flaws
|
|
204
|
+
- 1.0: The sole focus of their existence
|
|
205
|
+
|
|
206
|
+
Do not make micro-adjustments. Close enough is OK.
|
|
207
|
+
|
|
208
|
+
${categorySection}
|
|
209
|
+
|
|
210
|
+
${exposureSection}
|
|
211
|
+
|
|
212
|
+
## Quotes
|
|
213
|
+
|
|
214
|
+
In addition to updating the TOPIC, identify any **memorable, funny, important, or stand-out phrases** from the Most Recent Messages that relate to this TOPIC.
|
|
215
|
+
|
|
216
|
+
### What Makes a Quote Worth Preserving
|
|
217
|
+
|
|
218
|
+
**Prioritize:**
|
|
219
|
+
- Humor, wit, colorful language, creative profanity
|
|
220
|
+
- Emotional outbursts (positive or negative) — the raw stuff
|
|
221
|
+
- Phrases that reveal personality or communication style
|
|
222
|
+
- Things you'd quote back to them later to make them laugh
|
|
223
|
+
- Unique expressions, malaphors, or turns of phrase
|
|
224
|
+
- Quotable moments from EITHER speaker — humans AND AI personas both say memorable things
|
|
225
|
+
|
|
226
|
+
**NEVER extract these — they are NOT quotes:**
|
|
227
|
+
- Technical identifiers: ARNs, URLs, file paths, UUIDs, config keys, environment variable values, role/policy names
|
|
228
|
+
- AI agent self-talk: "I notice I'm in Plan Mode", "I'll start by...", "Let me help you with...", status updates about the agent's own process
|
|
229
|
+
- AI apologies or acknowledgments: "You're absolutely right", "I apologize for that overreach"
|
|
230
|
+
- Generic AI instructions or tips, tool usage advice, workflow suggestions
|
|
231
|
+
- Dry technical facts: infrastructure descriptions, process status, batch sizes, system architecture summaries
|
|
232
|
+
- Generic statements that could come from anyone or any AI session
|
|
233
|
+
- Credentials, secrets, connection strings, or anything that looks like an access token
|
|
234
|
+
|
|
235
|
+
**The litmus test**: Would you bring this up at a bar with a friend? Would it make someone laugh, think, or feel something?
|
|
236
|
+
- "Does the Pope shit in his hat?" → YES. Hilarious malaphor.
|
|
237
|
+
- "AWSReservedSSO_cmidp-nihl-sandbox-adm_db7b191e026bdd85" → NO. That's a credential.
|
|
238
|
+
- "Slow is smooth. Smooth is fast." → YES (once). Pithy wisdom.
|
|
239
|
+
- "The authentication flow is working correctly now" → NO. Status update.
|
|
240
|
+
|
|
241
|
+
**When in doubt, leave it out.** An empty quotes array is always acceptable.
|
|
242
|
+
|
|
243
|
+
**CRITICAL**: Return the EXACT text as it appears in the message. **WE CAN ONLY USE IT IF WE FIND IT IN THE TEXT.**
|
|
244
|
+
|
|
245
|
+
# CRITICAL INSTRUCTIONS
|
|
246
|
+
|
|
247
|
+
ONLY ANALYZE the "Most Recent Messages". The "Earlier Conversation" is provided for context only — it has already been processed.
|
|
248
|
+
|
|
249
|
+
\`\`\`json
|
|
250
|
+
${jsonTemplate}
|
|
251
|
+
\`\`\`
|
|
252
|
+
|
|
253
|
+
When returning a record, **ALWAYS** include \`name\`, \`description\`, and \`sentiment\`.
|
|
254
|
+
|
|
255
|
+
If you find **NO EVIDENCE** of this TOPIC in the "Most Recent Messages", respond with: \`{}\`
|
|
256
|
+
|
|
257
|
+
If **NO CHANGES** are required, respond with: \`{}\`
|
|
258
|
+
|
|
259
|
+
An empty object is the MOST COMMON expected response.
|
|
260
|
+
|
|
261
|
+
# Current Details of TOPIC
|
|
262
|
+
|
|
263
|
+
${currentDetailsSection}
|
|
264
|
+
|
|
265
|
+
${participantContextSection(data.participant_context)}`;
|
|
266
|
+
|
|
267
|
+
const earlierSection =
|
|
268
|
+
data.messages_context.length > 0
|
|
269
|
+
? `## Earlier Conversation
|
|
270
|
+
${formatMessagesAsPlaceholders(data.messages_context, personaName)}
|
|
271
|
+
|
|
272
|
+
`
|
|
273
|
+
: "";
|
|
274
|
+
|
|
275
|
+
const recentSection = `## Most Recent Messages
|
|
276
|
+
${formatMessagesAsPlaceholders(data.messages_analyze, personaName)}`;
|
|
277
|
+
|
|
278
|
+
const user = `# Conversation
|
|
279
|
+
${earlierSection}${recentSection}
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
Analyze the Most Recent Messages and update the TOPIC if warranted.
|
|
284
|
+
|
|
285
|
+
**Return JSON:**
|
|
286
|
+
\`\`\`json
|
|
287
|
+
${jsonTemplate}
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
If no changes are needed, respond with: \`{}\``;
|
|
291
|
+
|
|
292
|
+
return { system, user };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import type { Message
|
|
1
|
+
import type { Message } from "../../core/types.js";
|
|
2
2
|
|
|
3
3
|
export interface PromptOutput {
|
|
4
4
|
system: string;
|
|
5
5
|
user: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export interface ParticipantContext {
|
|
9
|
+
persona_name: string;
|
|
10
|
+
persona_description?: string; // long_description, omitted if empty
|
|
11
|
+
human_name?: string; // e.g. "Jeremy (Flare)" — omitted if no facts
|
|
12
|
+
human_age?: number; // calculated from Birthday fact — omitted if not set
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
interface BaseScanPromptData {
|
|
9
16
|
messages_context: Message[];
|
|
10
17
|
messages_analyze: Message[];
|
|
@@ -15,40 +22,65 @@ export interface FactScanPromptData extends BaseScanPromptData {}
|
|
|
15
22
|
|
|
16
23
|
export interface TraitScanPromptData extends BaseScanPromptData {}
|
|
17
24
|
|
|
18
|
-
export interface TopicScanPromptData extends BaseScanPromptData {
|
|
25
|
+
export interface TopicScanPromptData extends BaseScanPromptData {
|
|
26
|
+
participant_context?: ParticipantContext;
|
|
27
|
+
}
|
|
19
28
|
|
|
20
29
|
export interface PersonScanPromptData extends BaseScanPromptData {}
|
|
21
30
|
|
|
31
|
+
export interface FactFindPromptData {
|
|
32
|
+
persona_name: string;
|
|
33
|
+
missing_fact_names: string[]; // Built-in facts with no description
|
|
34
|
+
messages_context: Message[]; // Earlier conversation (already processed)
|
|
35
|
+
messages_analyze: Message[]; // Recent messages to scan
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
export interface FactScanCandidate {
|
|
23
39
|
type_of_fact: string;
|
|
24
40
|
value_of_fact: string;
|
|
25
41
|
reason: string;
|
|
26
42
|
}
|
|
27
43
|
|
|
28
|
-
export interface TraitScanCandidate {
|
|
29
|
-
type_of_trait: string;
|
|
30
|
-
value_of_trait: string;
|
|
31
|
-
reason: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
44
|
export interface TopicScanCandidate {
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
name: string;
|
|
46
|
+
description: string;
|
|
47
|
+
category: string;
|
|
37
48
|
reason: string;
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
export interface PersonScanCandidate {
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
name: string;
|
|
53
|
+
description: string;
|
|
54
|
+
relationship: string;
|
|
43
55
|
reason: string;
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
export interface
|
|
47
|
-
|
|
58
|
+
export interface TopicMatchPromptData {
|
|
59
|
+
candidate_name: string;
|
|
60
|
+
candidate_description: string;
|
|
61
|
+
candidate_category: string;
|
|
62
|
+
existing_topics: Array<{
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
description: string;
|
|
66
|
+
category?: string;
|
|
67
|
+
}>;
|
|
48
68
|
}
|
|
49
69
|
|
|
50
|
-
export interface
|
|
51
|
-
|
|
70
|
+
export interface PersonMatchPromptData {
|
|
71
|
+
candidate_name: string;
|
|
72
|
+
candidate_description: string;
|
|
73
|
+
candidate_relationship: string;
|
|
74
|
+
existing_people: Array<{
|
|
75
|
+
id: string;
|
|
76
|
+
name: string;
|
|
77
|
+
description: string;
|
|
78
|
+
relationship?: string;
|
|
79
|
+
}>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface FactScanResult {
|
|
83
|
+
facts: FactScanCandidate[];
|
|
52
84
|
}
|
|
53
85
|
|
|
54
86
|
export interface TopicScanResult {
|
|
@@ -59,15 +91,11 @@ export interface PersonScanResult {
|
|
|
59
91
|
people: PersonScanCandidate[];
|
|
60
92
|
}
|
|
61
93
|
|
|
62
|
-
export interface
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
data_type: DataItemType;
|
|
68
|
-
data_id: string;
|
|
69
|
-
data_name: string;
|
|
70
|
-
data_description: string;
|
|
94
|
+
export interface FactFindResult {
|
|
95
|
+
facts: Array<{
|
|
96
|
+
name: string; // Must match a name from missing_fact_names
|
|
97
|
+
value: string; // The extracted value
|
|
98
|
+
evidence: string; // Direct quote/reference (NOT stored, limits hallucination)
|
|
71
99
|
}>;
|
|
72
100
|
}
|
|
73
101
|
|
|
@@ -75,16 +103,6 @@ export interface ItemMatchResult {
|
|
|
75
103
|
matched_guid: string | null;
|
|
76
104
|
}
|
|
77
105
|
|
|
78
|
-
export interface ItemUpdatePromptData {
|
|
79
|
-
data_type: DataItemType;
|
|
80
|
-
existing_item: DataItemBase | null;
|
|
81
|
-
messages_context: Message[];
|
|
82
|
-
messages_analyze: Message[];
|
|
83
|
-
persona_name: string;
|
|
84
|
-
new_item_name?: string;
|
|
85
|
-
new_item_value?: string;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
106
|
export type ExposureImpact = "high" | "medium" | "low" | "none";
|
|
89
107
|
|
|
90
108
|
export interface QuoteCandidate {
|
|
@@ -101,10 +119,6 @@ export interface ItemUpdateResultBase {
|
|
|
101
119
|
|
|
102
120
|
export interface FactUpdateResult extends ItemUpdateResultBase {}
|
|
103
121
|
|
|
104
|
-
export interface TraitUpdateResult extends ItemUpdateResultBase {
|
|
105
|
-
strength?: number;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
122
|
export interface TopicUpdateResult extends ItemUpdateResultBase {
|
|
109
123
|
category?: string;
|
|
110
124
|
exposure_desired?: number;
|
|
@@ -119,7 +133,16 @@ export interface PersonUpdateResult extends ItemUpdateResultBase {
|
|
|
119
133
|
|
|
120
134
|
export type ItemUpdateResult =
|
|
121
135
|
| FactUpdateResult
|
|
122
|
-
| TraitUpdateResult
|
|
123
136
|
| TopicUpdateResult
|
|
124
137
|
| PersonUpdateResult
|
|
125
138
|
| Record<string, never>;
|
|
139
|
+
|
|
140
|
+
export interface EventScanCandidate {
|
|
141
|
+
name: string;
|
|
142
|
+
description: string;
|
|
143
|
+
reason: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface EventScanResult {
|
|
147
|
+
events: EventScanCandidate[];
|
|
148
|
+
}
|
package/src/prompts/index.ts
CHANGED
|
@@ -46,28 +46,24 @@ export type {
|
|
|
46
46
|
|
|
47
47
|
export {
|
|
48
48
|
buildHumanFactScanPrompt,
|
|
49
|
-
buildHumanTraitScanPrompt,
|
|
50
49
|
buildHumanTopicScanPrompt,
|
|
51
50
|
buildHumanPersonScanPrompt,
|
|
52
|
-
|
|
53
|
-
buildHumanItemUpdatePrompt,
|
|
51
|
+
buildEventScanPrompt,
|
|
54
52
|
} from "./human/index.js";
|
|
53
|
+
export type { EventScanPromptData } from "./human/event-scan.js";
|
|
55
54
|
export type {
|
|
56
55
|
FactScanPromptData,
|
|
57
|
-
TraitScanPromptData,
|
|
58
56
|
TopicScanPromptData,
|
|
59
57
|
PersonScanPromptData,
|
|
60
58
|
FactScanCandidate,
|
|
61
|
-
TraitScanCandidate,
|
|
62
59
|
TopicScanCandidate,
|
|
63
60
|
PersonScanCandidate,
|
|
61
|
+
EventScanCandidate,
|
|
62
|
+
EventScanResult,
|
|
64
63
|
FactScanResult,
|
|
65
|
-
TraitScanResult,
|
|
66
64
|
TopicScanResult,
|
|
67
65
|
PersonScanResult,
|
|
68
|
-
ItemMatchPromptData,
|
|
69
66
|
ItemMatchResult,
|
|
70
|
-
ItemUpdatePromptData,
|
|
71
67
|
ExposureImpact,
|
|
72
68
|
ItemUpdateResult,
|
|
73
69
|
} from "./human/types.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PersonaTopicUpdatePromptData, PromptOutput } from "./types.js";
|
|
2
|
-
import type { PersonaTopic,
|
|
2
|
+
import type { PersonaTopic, PersonaTrait } from "../../core/types.js";
|
|
3
3
|
import { formatMessagesAsPlaceholders } from "../message-utils.js";
|
|
4
4
|
|
|
5
|
-
function formatTraitsForPrompt(traits:
|
|
5
|
+
function formatTraitsForPrompt(traits: PersonaTrait[]): string {
|
|
6
6
|
if (traits.length === 0) return "(No traits defined)";
|
|
7
7
|
return traits.map(t => `- ${t.name}: ${t.description}`).join('\n');
|
|
8
8
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PersonaTraitExtractionPromptData, PromptOutput } from "./types.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { PersonaTrait } from "../../core/types.js";
|
|
3
3
|
import { formatMessagesAsPlaceholders } from "../message-utils.js";
|
|
4
4
|
|
|
5
|
-
function formatTraitsForPrompt(traits:
|
|
5
|
+
function formatTraitsForPrompt(traits: PersonaTrait[]): string {
|
|
6
6
|
if (traits.length === 0) return "(No traits yet)";
|
|
7
7
|
|
|
8
8
|
return JSON.stringify(traits.map(t => ({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PersonaTrait, Message, PersonaTopic } from "../../core/types.js";
|
|
2
2
|
|
|
3
3
|
export interface PromptOutput {
|
|
4
4
|
system: string;
|
|
@@ -7,7 +7,7 @@ export interface PromptOutput {
|
|
|
7
7
|
|
|
8
8
|
export interface PersonaTraitExtractionPromptData {
|
|
9
9
|
persona_name: string;
|
|
10
|
-
current_traits:
|
|
10
|
+
current_traits: PersonaTrait[];
|
|
11
11
|
messages_context: Message[];
|
|
12
12
|
messages_analyze: Message[];
|
|
13
13
|
}
|
|
@@ -56,7 +56,7 @@ export interface PersonaTopicUpdatePromptData {
|
|
|
56
56
|
persona_name: string;
|
|
57
57
|
short_description?: string;
|
|
58
58
|
long_description?: string;
|
|
59
|
-
traits:
|
|
59
|
+
traits: PersonaTrait[];
|
|
60
60
|
existing_topic?: PersonaTopic; // If updating existing
|
|
61
61
|
candidate: PersonaTopicScanCandidate;
|
|
62
62
|
messages_context: Message[];
|
|
@@ -34,7 +34,7 @@ function buildEiSystemPrompt(data: ResponsePromptData): string {
|
|
|
34
34
|
You are the central hub of this experience - a thoughtful AI who genuinely cares about the human's wellbeing and growth. You listen, remember, and help them reflect. You're curious about their life but never intrusive.
|
|
35
35
|
|
|
36
36
|
Your role is unique among personas:
|
|
37
|
-
- You see ALL of the human's data (facts,
|
|
37
|
+
- You see ALL of the human's data (facts, topics, people) across all groups
|
|
38
38
|
- You help them understand and navigate the system
|
|
39
39
|
- You gently help them explore their thoughts and feelings
|
|
40
40
|
- You attempt to emulate their speech patterns;
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
* Building blocks for constructing response prompts
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { PersonaTrait, Quote, PersonaTopic } from "../../core/types.js";
|
|
7
7
|
import type { ResponsePromptData } from "./types.js";
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
const DESCRIPTION_MAX_CHARS = 500;
|
|
10
|
+
|
|
11
|
+
function truncateDescription(description: string): string {
|
|
12
|
+
if (description.length <= DESCRIPTION_MAX_CHARS) return description;
|
|
13
|
+
return description.slice(0, DESCRIPTION_MAX_CHARS) + "…";
|
|
14
|
+
}
|
|
9
15
|
|
|
10
16
|
// =============================================================================
|
|
11
17
|
// IDENTITY SECTION
|
|
@@ -62,7 +68,7 @@ export function buildGuidelinesSection(personaName: string): string {
|
|
|
62
68
|
// TRAITS SECTION
|
|
63
69
|
// =============================================================================
|
|
64
70
|
|
|
65
|
-
export function buildTraitsSection(traits:
|
|
71
|
+
export function buildTraitsSection(traits: PersonaTrait[], header: string): string {
|
|
66
72
|
if (traits.length === 0) return "";
|
|
67
73
|
|
|
68
74
|
const sorted = [...traits].sort((a, b) => (b.strength ?? 0.5) - (a.strength ?? 0.5)).slice(0, 15);
|
|
@@ -146,14 +152,6 @@ export function buildHumanSection(human: ResponsePromptData["human"]): string {
|
|
|
146
152
|
if (facts) sections.push(`### Key Facts\n${facts}`);
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
// Traits
|
|
150
|
-
if (human.traits.length > 0) {
|
|
151
|
-
const traits = human.traits
|
|
152
|
-
.slice(0, 15)
|
|
153
|
-
.map(t => `- **${t.name}**: ${truncateDescription(t.description)}`)
|
|
154
|
-
.join("\n");
|
|
155
|
-
sections.push(`### Personality\n${traits}`);
|
|
156
|
-
}
|
|
157
155
|
|
|
158
156
|
// Active topics (exposure_current > 0.3)
|
|
159
157
|
const activeTopics = human.topics.filter(t => t.exposure_current > 0.3);
|
|
@@ -275,7 +273,6 @@ export function buildQuotesSection(quotes: Quote[], human: ResponsePromptData["h
|
|
|
275
273
|
|
|
276
274
|
const allDataItems = [
|
|
277
275
|
...human.facts.map(f => ({ id: f.id, name: f.name })),
|
|
278
|
-
...human.traits.map(t => ({ id: t.id, name: t.name })),
|
|
279
276
|
...human.topics.map(t => ({ id: t.id, name: t.name })),
|
|
280
277
|
...human.people.map(p => ({ id: p.id, name: p.name })),
|
|
281
278
|
];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Based on CONTRACTS.md ResponsePromptData specification
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { Fact,
|
|
6
|
+
import type { Fact, PersonaTrait, Topic, Person, Quote, PersonaTopic } from "../../core/types.js";
|
|
7
7
|
import type { ToolDefinition } from "../../core/types.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -15,12 +15,11 @@ export interface ResponsePromptData {
|
|
|
15
15
|
aliases: string[];
|
|
16
16
|
short_description?: string;
|
|
17
17
|
long_description?: string;
|
|
18
|
-
traits:
|
|
18
|
+
traits: PersonaTrait[];
|
|
19
19
|
topics: PersonaTopic[];
|
|
20
20
|
};
|
|
21
21
|
human: {
|
|
22
22
|
facts: Fact[];
|
|
23
|
-
traits: Trait[];
|
|
24
23
|
topics: Topic[];
|
|
25
24
|
people: Person[];
|
|
26
25
|
quotes: Quote[];
|
|
@@ -56,7 +56,7 @@ function decodeEmbedding(value: unknown): number[] | undefined {
|
|
|
56
56
|
// Walk the entire StorageState and encode/decode all embedding fields
|
|
57
57
|
// ---------------------------------------------------------------------------
|
|
58
58
|
|
|
59
|
-
const HUMAN_ITEM_KEYS = ["facts", "
|
|
59
|
+
const HUMAN_ITEM_KEYS = ["facts", "topics", "people", "quotes"] as const;
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Returns a new StorageState with embeddings encoded as base64 strings.
|
package/src/storage/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type { Storage } from "./interface.js";
|
|
2
2
|
export { LocalStorage } from "./local.js";
|
|
3
|
+
export { IndexedDBStorage } from "./indexed.js";
|
|
3
4
|
export { remoteSync, RemoteSync, type RemoteSyncCredentials, type RemoteTimestamp, type SyncResult, type FetchResult } from "./remote.js";
|
|
4
5
|
export { encrypt, decrypt, generateUserId, type CryptoCredentials, type EncryptedPayload } from "./crypto.js";
|
|
5
6
|
export { yoloMerge } from "./merge.js";
|