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.
Files changed (88) hide show
  1. package/README.md +42 -0
  2. package/package.json +2 -1
  3. package/src/README.md +4 -11
  4. package/src/cli/README.md +87 -7
  5. package/src/cli/commands/facts.ts +2 -2
  6. package/src/cli/commands/people.ts +2 -2
  7. package/src/cli/commands/quotes.ts +2 -2
  8. package/src/cli/commands/topics.ts +2 -2
  9. package/src/cli/mcp.ts +94 -0
  10. package/src/cli/retrieval.ts +67 -31
  11. package/src/cli.ts +64 -23
  12. package/src/core/AGENTS.md +1 -1
  13. package/src/core/constants/built-in-facts.ts +49 -0
  14. package/src/core/constants/index.ts +1 -0
  15. package/src/core/context-utils.ts +0 -1
  16. package/src/core/embedding-service.ts +8 -0
  17. package/src/core/handlers/dedup.ts +11 -23
  18. package/src/core/handlers/heartbeat.ts +2 -3
  19. package/src/core/handlers/human-extraction.ts +96 -30
  20. package/src/core/handlers/human-matching.ts +328 -248
  21. package/src/core/handlers/index.ts +8 -6
  22. package/src/core/handlers/persona-generation.ts +8 -8
  23. package/src/core/handlers/rewrite.ts +4 -51
  24. package/src/core/handlers/utils.ts +23 -1
  25. package/src/core/heartbeat-manager.ts +2 -4
  26. package/src/core/human-data-manager.ts +38 -36
  27. package/src/core/message-manager.ts +10 -10
  28. package/src/core/orchestrators/ceremony.ts +49 -44
  29. package/src/core/orchestrators/dedup-phase.ts +2 -4
  30. package/src/core/orchestrators/human-extraction.ts +351 -207
  31. package/src/core/orchestrators/index.ts +6 -4
  32. package/src/core/orchestrators/persona-generation.ts +3 -3
  33. package/src/core/processor.ts +167 -20
  34. package/src/core/prompt-context-builder.ts +4 -6
  35. package/src/core/state/human.ts +1 -26
  36. package/src/core/state/personas.ts +2 -2
  37. package/src/core/state-manager.ts +107 -14
  38. package/src/core/tools/builtin/read-memory.ts +13 -18
  39. package/src/core/types/data-items.ts +3 -4
  40. package/src/core/types/entities.ts +7 -4
  41. package/src/core/types/enums.ts +6 -9
  42. package/src/core/types/llm.ts +2 -2
  43. package/src/core/utils/crossFind.ts +2 -5
  44. package/src/core/utils/event-windows.ts +31 -0
  45. package/src/integrations/claude-code/importer.ts +14 -5
  46. package/src/integrations/claude-code/types.ts +3 -0
  47. package/src/integrations/cursor/importer.ts +282 -0
  48. package/src/integrations/cursor/index.ts +10 -0
  49. package/src/integrations/cursor/reader.ts +209 -0
  50. package/src/integrations/cursor/types.ts +140 -0
  51. package/src/integrations/opencode/importer.ts +14 -4
  52. package/src/prompts/AGENTS.md +73 -1
  53. package/src/prompts/ceremony/dedup.ts +0 -33
  54. package/src/prompts/ceremony/rewrite.ts +6 -41
  55. package/src/prompts/ceremony/types.ts +4 -4
  56. package/src/prompts/generation/descriptions.ts +2 -2
  57. package/src/prompts/generation/types.ts +2 -2
  58. package/src/prompts/heartbeat/types.ts +2 -2
  59. package/src/prompts/human/event-scan.ts +122 -0
  60. package/src/prompts/human/fact-find.ts +106 -0
  61. package/src/prompts/human/fact-scan.ts +0 -2
  62. package/src/prompts/human/index.ts +17 -10
  63. package/src/prompts/human/person-match.ts +65 -0
  64. package/src/prompts/human/person-scan.ts +52 -59
  65. package/src/prompts/human/person-update.ts +241 -0
  66. package/src/prompts/human/topic-match.ts +65 -0
  67. package/src/prompts/human/topic-scan.ts +51 -71
  68. package/src/prompts/human/topic-update.ts +295 -0
  69. package/src/prompts/human/types.ts +63 -40
  70. package/src/prompts/index.ts +4 -8
  71. package/src/prompts/persona/topics-update.ts +2 -2
  72. package/src/prompts/persona/traits.ts +2 -2
  73. package/src/prompts/persona/types.ts +3 -3
  74. package/src/prompts/response/index.ts +1 -1
  75. package/src/prompts/response/sections.ts +9 -12
  76. package/src/prompts/response/types.ts +2 -3
  77. package/src/storage/embeddings.ts +1 -1
  78. package/src/storage/index.ts +1 -0
  79. package/src/storage/indexed.ts +174 -0
  80. package/src/storage/merge.ts +67 -2
  81. package/tui/src/commands/me.tsx +5 -14
  82. package/tui/src/commands/settings.tsx +15 -0
  83. package/tui/src/context/ei.tsx +5 -14
  84. package/tui/src/util/yaml-serializers.ts +76 -33
  85. package/src/cli/commands/traits.ts +0 -25
  86. package/src/prompts/human/item-match.ts +0 -74
  87. package/src/prompts/human/item-update.ts +0 -364
  88. 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, DataItemBase, DataItemType } from "../../core/types.js";
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
- type_of_topic: string;
36
- value_of_topic: string;
45
+ name: string;
46
+ description: string;
47
+ category: string;
37
48
  reason: string;
38
49
  }
39
50
 
40
51
  export interface PersonScanCandidate {
41
- type_of_person: string;
42
- name_of_person: string;
52
+ name: string;
53
+ description: string;
54
+ relationship: string;
43
55
  reason: string;
44
56
  }
45
57
 
46
- export interface FactScanResult {
47
- facts: FactScanCandidate[];
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 TraitScanResult {
51
- traits: TraitScanCandidate[];
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 ItemMatchPromptData {
63
- candidate_type: DataItemType;
64
- candidate_name: string;
65
- candidate_value: string;
66
- all_items: Array<{
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
+ }
@@ -46,28 +46,24 @@ export type {
46
46
 
47
47
  export {
48
48
  buildHumanFactScanPrompt,
49
- buildHumanTraitScanPrompt,
50
49
  buildHumanTopicScanPrompt,
51
50
  buildHumanPersonScanPrompt,
52
- buildHumanItemMatchPrompt,
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, Trait } from "../../core/types.js";
2
+ import type { PersonaTopic, PersonaTrait } from "../../core/types.js";
3
3
  import { formatMessagesAsPlaceholders } from "../message-utils.js";
4
4
 
5
- function formatTraitsForPrompt(traits: Trait[]): string {
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 { Trait } from "../../core/types.js";
2
+ import type { PersonaTrait } from "../../core/types.js";
3
3
  import { formatMessagesAsPlaceholders } from "../message-utils.js";
4
4
 
5
- function formatTraitsForPrompt(traits: Trait[]): string {
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 { Trait, Message, PersonaTopic } from "../../core/types.js";
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: Trait[];
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: Trait[];
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, traits, topics, people) across all groups
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 { Trait, Quote, PersonaTopic } from "../../core/types.js";
6
+ import type { PersonaTrait, Quote, PersonaTopic } from "../../core/types.js";
7
7
  import type { ResponsePromptData } from "./types.js";
8
- import { truncateDescription } from "../human/item-update.js";
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: Trait[], header: string): string {
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, Trait, Topic, Person, Quote, PersonaTopic } from "../../core/types.js";
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: Trait[];
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", "traits", "topics", "people", "quotes"] as const;
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.
@@ -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";