ei-tui 0.1.3
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/LICENSE +21 -0
- package/README.md +170 -0
- package/package.json +63 -0
- package/src/README.md +96 -0
- package/src/cli/README.md +47 -0
- package/src/cli/commands/facts.ts +25 -0
- package/src/cli/commands/people.ts +25 -0
- package/src/cli/commands/quotes.ts +19 -0
- package/src/cli/commands/topics.ts +25 -0
- package/src/cli/commands/traits.ts +25 -0
- package/src/cli/retrieval.ts +269 -0
- package/src/cli.ts +176 -0
- package/src/core/AGENTS.md +104 -0
- package/src/core/embedding-service.ts +241 -0
- package/src/core/handlers/index.ts +1057 -0
- package/src/core/index.ts +4 -0
- package/src/core/llm-client.ts +265 -0
- package/src/core/model-context-windows.ts +49 -0
- package/src/core/orchestrators/ceremony.ts +500 -0
- package/src/core/orchestrators/extraction-chunker.ts +138 -0
- package/src/core/orchestrators/human-extraction.ts +457 -0
- package/src/core/orchestrators/index.ts +28 -0
- package/src/core/orchestrators/persona-generation.ts +76 -0
- package/src/core/orchestrators/persona-topics.ts +117 -0
- package/src/core/personas/index.ts +5 -0
- package/src/core/personas/opencode-agent.ts +81 -0
- package/src/core/processor.ts +1413 -0
- package/src/core/queue-processor.ts +197 -0
- package/src/core/state/checkpoints.ts +68 -0
- package/src/core/state/human.ts +176 -0
- package/src/core/state/index.ts +5 -0
- package/src/core/state/personas.ts +217 -0
- package/src/core/state/queue.ts +144 -0
- package/src/core/state-manager.ts +347 -0
- package/src/core/types.ts +421 -0
- package/src/core/utils/decay.ts +33 -0
- package/src/index.ts +1 -0
- package/src/integrations/opencode/importer.ts +896 -0
- package/src/integrations/opencode/index.ts +16 -0
- package/src/integrations/opencode/json-reader.ts +304 -0
- package/src/integrations/opencode/reader-factory.ts +35 -0
- package/src/integrations/opencode/sqlite-reader.ts +189 -0
- package/src/integrations/opencode/types.ts +244 -0
- package/src/prompts/AGENTS.md +62 -0
- package/src/prompts/ceremony/description-check.ts +47 -0
- package/src/prompts/ceremony/expire.ts +30 -0
- package/src/prompts/ceremony/explore.ts +60 -0
- package/src/prompts/ceremony/index.ts +11 -0
- package/src/prompts/ceremony/types.ts +42 -0
- package/src/prompts/generation/descriptions.ts +91 -0
- package/src/prompts/generation/index.ts +15 -0
- package/src/prompts/generation/persona.ts +155 -0
- package/src/prompts/generation/seeds.ts +31 -0
- package/src/prompts/generation/types.ts +47 -0
- package/src/prompts/heartbeat/check.ts +179 -0
- package/src/prompts/heartbeat/ei.ts +208 -0
- package/src/prompts/heartbeat/index.ts +15 -0
- package/src/prompts/heartbeat/types.ts +70 -0
- package/src/prompts/human/fact-scan.ts +152 -0
- package/src/prompts/human/index.ts +32 -0
- package/src/prompts/human/item-match.ts +74 -0
- package/src/prompts/human/item-update.ts +322 -0
- package/src/prompts/human/person-scan.ts +115 -0
- package/src/prompts/human/topic-scan.ts +135 -0
- package/src/prompts/human/trait-scan.ts +115 -0
- package/src/prompts/human/types.ts +127 -0
- package/src/prompts/index.ts +90 -0
- package/src/prompts/message-utils.ts +39 -0
- package/src/prompts/persona/index.ts +16 -0
- package/src/prompts/persona/topics-match.ts +69 -0
- package/src/prompts/persona/topics-scan.ts +98 -0
- package/src/prompts/persona/topics-update.ts +157 -0
- package/src/prompts/persona/traits.ts +117 -0
- package/src/prompts/persona/types.ts +74 -0
- package/src/prompts/response/index.ts +147 -0
- package/src/prompts/response/sections.ts +355 -0
- package/src/prompts/response/types.ts +38 -0
- package/src/prompts/validation/ei.ts +93 -0
- package/src/prompts/validation/index.ts +6 -0
- package/src/prompts/validation/types.ts +22 -0
- package/src/storage/crypto.ts +96 -0
- package/src/storage/index.ts +5 -0
- package/src/storage/interface.ts +9 -0
- package/src/storage/local.ts +79 -0
- package/src/storage/merge.ts +69 -0
- package/src/storage/remote.ts +145 -0
- package/src/templates/welcome.ts +91 -0
- package/tui/README.md +62 -0
- package/tui/bunfig.toml +4 -0
- package/tui/src/app.tsx +55 -0
- package/tui/src/commands/archive.tsx +93 -0
- package/tui/src/commands/context.tsx +124 -0
- package/tui/src/commands/delete.tsx +71 -0
- package/tui/src/commands/details.tsx +41 -0
- package/tui/src/commands/editor.tsx +46 -0
- package/tui/src/commands/help.tsx +12 -0
- package/tui/src/commands/me.tsx +145 -0
- package/tui/src/commands/model.ts +47 -0
- package/tui/src/commands/new.ts +31 -0
- package/tui/src/commands/pause.ts +46 -0
- package/tui/src/commands/persona.tsx +58 -0
- package/tui/src/commands/provider.tsx +124 -0
- package/tui/src/commands/quit.ts +22 -0
- package/tui/src/commands/quotes.tsx +172 -0
- package/tui/src/commands/registry.test.ts +137 -0
- package/tui/src/commands/registry.ts +130 -0
- package/tui/src/commands/resume.ts +39 -0
- package/tui/src/commands/setsync.tsx +43 -0
- package/tui/src/commands/settings.tsx +83 -0
- package/tui/src/components/ConfirmOverlay.tsx +51 -0
- package/tui/src/components/ConflictOverlay.tsx +78 -0
- package/tui/src/components/HelpOverlay.tsx +69 -0
- package/tui/src/components/Layout.tsx +24 -0
- package/tui/src/components/MessageList.tsx +174 -0
- package/tui/src/components/PersonaListOverlay.tsx +186 -0
- package/tui/src/components/PromptInput.tsx +145 -0
- package/tui/src/components/ProviderListOverlay.tsx +208 -0
- package/tui/src/components/QuotesOverlay.tsx +157 -0
- package/tui/src/components/Sidebar.tsx +95 -0
- package/tui/src/components/StatusBar.tsx +77 -0
- package/tui/src/components/WelcomeOverlay.tsx +73 -0
- package/tui/src/context/ei.tsx +623 -0
- package/tui/src/context/keyboard.tsx +164 -0
- package/tui/src/context/overlay.tsx +53 -0
- package/tui/src/index.tsx +8 -0
- package/tui/src/storage/file.ts +185 -0
- package/tui/src/util/duration.ts +32 -0
- package/tui/src/util/editor.ts +188 -0
- package/tui/src/util/logger.ts +109 -0
- package/tui/src/util/persona-editor.tsx +181 -0
- package/tui/src/util/provider-editor.tsx +168 -0
- package/tui/src/util/syntax.ts +35 -0
- package/tui/src/util/yaml-serializers.ts +755 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Integration Types
|
|
3
|
+
*
|
|
4
|
+
* These types represent the data structures read from OpenCode's storage.
|
|
5
|
+
* They are based on the actual file format in ~/.local/share/opencode/storage/
|
|
6
|
+
* and the SQLite database in ~/.local/share/opencode/opencode.db (1.2+)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Reader Interface
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Common interface for reading OpenCode data.
|
|
15
|
+
* Implemented by both JsonReader (legacy) and SqliteReader (1.2+).
|
|
16
|
+
*/
|
|
17
|
+
export interface IOpenCodeReader {
|
|
18
|
+
getSessionsUpdatedSince(since: Date): Promise<OpenCodeSession[]>;
|
|
19
|
+
getSessionsInRange(from: Date, to: Date): Promise<OpenCodeSession[]>;
|
|
20
|
+
getMessagesForSession(sessionId: string, since?: Date): Promise<OpenCodeMessage[]>;
|
|
21
|
+
getAgentInfo(agentName: string): Promise<OpenCodeAgent | null>;
|
|
22
|
+
getAllUniqueAgents(sessionId: string): Promise<string[]>;
|
|
23
|
+
getFirstAgent(sessionId: string): Promise<string | null>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Session Types (storage/session/{project_hash}/ses_xxx.json)
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Raw session data as stored by OpenCode
|
|
32
|
+
*/
|
|
33
|
+
export interface OpenCodeSessionRaw {
|
|
34
|
+
id: string; // ses_xxx
|
|
35
|
+
slug: string;
|
|
36
|
+
version: string;
|
|
37
|
+
projectID: string; // git root commit hash or "global"
|
|
38
|
+
directory: string; // Full path to project
|
|
39
|
+
parentID?: string; // Parent session ID if this is a subagent session
|
|
40
|
+
title: string;
|
|
41
|
+
time: {
|
|
42
|
+
created: number; // Unix timestamp ms
|
|
43
|
+
updated: number; // Unix timestamp ms
|
|
44
|
+
archived?: number; // Only set by web app
|
|
45
|
+
};
|
|
46
|
+
summary?: {
|
|
47
|
+
additions: number;
|
|
48
|
+
deletions: number;
|
|
49
|
+
files: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Cleaned session data for Ei consumption
|
|
55
|
+
*/
|
|
56
|
+
export interface OpenCodeSession {
|
|
57
|
+
id: string; // ses_xxx
|
|
58
|
+
title: string;
|
|
59
|
+
directory: string;
|
|
60
|
+
projectId: string;
|
|
61
|
+
parentId?: string;
|
|
62
|
+
time: {
|
|
63
|
+
created: number;
|
|
64
|
+
updated: number;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Message Types (storage/message/{session_id}/msg_xxx.json)
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Raw message data as stored by OpenCode
|
|
74
|
+
*/
|
|
75
|
+
export interface OpenCodeMessageRaw {
|
|
76
|
+
id: string; // msg_xxx
|
|
77
|
+
sessionID: string;
|
|
78
|
+
role: "user" | "assistant";
|
|
79
|
+
time: {
|
|
80
|
+
created: number;
|
|
81
|
+
completed?: number;
|
|
82
|
+
};
|
|
83
|
+
agent: string; // "build", "sisyphus", "librarian", etc.
|
|
84
|
+
parentID?: string;
|
|
85
|
+
model?: {
|
|
86
|
+
providerID: string;
|
|
87
|
+
modelID: string;
|
|
88
|
+
};
|
|
89
|
+
modelID?: string;
|
|
90
|
+
providerID?: string;
|
|
91
|
+
mode?: string;
|
|
92
|
+
cost?: number;
|
|
93
|
+
tokens?: {
|
|
94
|
+
input: number;
|
|
95
|
+
output: number;
|
|
96
|
+
reasoning: number;
|
|
97
|
+
cache?: {
|
|
98
|
+
read: number;
|
|
99
|
+
write: number;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
finish?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Cleaned message data for Ei consumption
|
|
107
|
+
*/
|
|
108
|
+
export interface OpenCodeMessage {
|
|
109
|
+
id: string; // msg_xxx
|
|
110
|
+
sessionId: string;
|
|
111
|
+
role: "user" | "assistant";
|
|
112
|
+
agent: string;
|
|
113
|
+
content: string; // Filtered, concatenated text parts
|
|
114
|
+
timestamp: string; // ISO string from time.created
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Part Types (storage/part/{msg_id}/prt_xxx.json)
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Raw part data as stored by OpenCode
|
|
123
|
+
* Parts can be of various types, but we only care about text parts
|
|
124
|
+
*/
|
|
125
|
+
export interface OpenCodePartRaw {
|
|
126
|
+
id: string; // prt_xxx
|
|
127
|
+
sessionID: string;
|
|
128
|
+
messageID: string;
|
|
129
|
+
type: "text" | "tool" | "file" | "step-start" | "step-finish" | string;
|
|
130
|
+
text?: string; // Only present for type="text"
|
|
131
|
+
synthetic?: boolean; // true = tool call summary, skip
|
|
132
|
+
time?: {
|
|
133
|
+
start: number;
|
|
134
|
+
end: number;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// Agent Types
|
|
140
|
+
// ============================================================================
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Agent info for persona creation
|
|
144
|
+
*/
|
|
145
|
+
export interface OpenCodeAgent {
|
|
146
|
+
name: string;
|
|
147
|
+
description?: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Utility agents that should not have personas created for them.
|
|
152
|
+
* These are internal agents that do housekeeping tasks.
|
|
153
|
+
*/
|
|
154
|
+
export const UTILITY_AGENTS = ["compaction", "title", "summary"] as const;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Message content prefixes that indicate agent-to-agent communication.
|
|
158
|
+
* Messages starting with these should be filtered out during import.
|
|
159
|
+
*/
|
|
160
|
+
export const AGENT_TO_AGENT_PREFIXES = [
|
|
161
|
+
"[search-mode]",
|
|
162
|
+
"[analyze-mode]",
|
|
163
|
+
"[CONTEXT]",
|
|
164
|
+
"<analysis>",
|
|
165
|
+
"<results>",
|
|
166
|
+
] as const;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Agent name aliases for consolidating variants into a single persona.
|
|
170
|
+
* OpenCode's plugin ecosystem results in the same logical agent having
|
|
171
|
+
* different names across versions/configs (e.g., "sisyphus", "Sisyphus",
|
|
172
|
+
* "Sisyphus (Ultraworker)", "Planner-Sisyphus" are all the same agent).
|
|
173
|
+
*
|
|
174
|
+
* Key = canonical name (used for display_name, Title Case)
|
|
175
|
+
* Value = array of variants that should resolve to this persona
|
|
176
|
+
*/
|
|
177
|
+
export const AGENT_ALIASES: Record<string, string[]> = {
|
|
178
|
+
Sisyphus: [
|
|
179
|
+
"sisyphus",
|
|
180
|
+
"Sisyphus",
|
|
181
|
+
"Sisyphus (Ultraworker)",
|
|
182
|
+
"Planner-Sisyphus",
|
|
183
|
+
"planner-sisyphus",
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Built-in agent definitions (fallback when config unavailable)
|
|
189
|
+
*/
|
|
190
|
+
export const BUILTIN_AGENTS: Record<string, OpenCodeAgent> = {
|
|
191
|
+
build: {
|
|
192
|
+
name: "build",
|
|
193
|
+
description: "The main coding agent that implements features and fixes bugs",
|
|
194
|
+
},
|
|
195
|
+
sisyphus: {
|
|
196
|
+
name: "sisyphus",
|
|
197
|
+
description: "Powerful AI Agent with orchestration capabilities",
|
|
198
|
+
},
|
|
199
|
+
plan: {
|
|
200
|
+
name: "plan",
|
|
201
|
+
description: "Creates implementation plans for complex features",
|
|
202
|
+
},
|
|
203
|
+
general: {
|
|
204
|
+
name: "general",
|
|
205
|
+
description: "General purpose assistant for non-coding tasks",
|
|
206
|
+
},
|
|
207
|
+
explore: {
|
|
208
|
+
name: "explore",
|
|
209
|
+
description: "Explores codebases to understand structure and patterns",
|
|
210
|
+
},
|
|
211
|
+
librarian: {
|
|
212
|
+
name: "librarian",
|
|
213
|
+
description:
|
|
214
|
+
"Searches external references including documentation and open source examples",
|
|
215
|
+
},
|
|
216
|
+
compaction: {
|
|
217
|
+
name: "compaction",
|
|
218
|
+
description: "Compacts conversation history to save context",
|
|
219
|
+
},
|
|
220
|
+
title: {
|
|
221
|
+
name: "title",
|
|
222
|
+
description: "Generates session titles",
|
|
223
|
+
},
|
|
224
|
+
summary: {
|
|
225
|
+
name: "summary",
|
|
226
|
+
description: "Generates session summaries",
|
|
227
|
+
},
|
|
228
|
+
oracle: {
|
|
229
|
+
name: "oracle",
|
|
230
|
+
description: "High-IQ read-only consultant for architecture and debugging",
|
|
231
|
+
},
|
|
232
|
+
atlas: {
|
|
233
|
+
name: "atlas",
|
|
234
|
+
description: "Task execution agent for delegated work",
|
|
235
|
+
},
|
|
236
|
+
metis: {
|
|
237
|
+
name: "metis",
|
|
238
|
+
description: "Pre-planning consultant for scope clarification",
|
|
239
|
+
},
|
|
240
|
+
momus: {
|
|
241
|
+
name: "momus",
|
|
242
|
+
description: "Expert reviewer for evaluating work plans",
|
|
243
|
+
},
|
|
244
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Prompts Module
|
|
2
|
+
|
|
3
|
+
LLM prompt builders. All prompts are **synchronous** and **pure**.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
prompts/
|
|
9
|
+
├── index.ts # Re-exports all builders
|
|
10
|
+
├── message-utils.ts # Format messages for context
|
|
11
|
+
├── ceremony/ # Exposure decay, persona enrichment
|
|
12
|
+
├── generation/ # New persona creation
|
|
13
|
+
├── heartbeat/ # Periodic check-ins
|
|
14
|
+
├── human/ # Fact/trait/topic/person extraction
|
|
15
|
+
├── persona/ # Persona trait extraction, topic matching
|
|
16
|
+
├── response/ # Main conversation responses
|
|
17
|
+
└── validation/ # Data validation prompts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Pattern
|
|
21
|
+
|
|
22
|
+
Every prompt builder follows this contract:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
interface PromptBuilder<T> {
|
|
26
|
+
(data: T): { system: string; user: string }
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Rules**:
|
|
31
|
+
1. **Synchronous** - No async, no fetching
|
|
32
|
+
2. **Pure** - Same input → same output
|
|
33
|
+
3. **Pre-processed data** - Processor fetches/filters before calling
|
|
34
|
+
4. **Minimal logic** - String interpolation, not computation
|
|
35
|
+
|
|
36
|
+
## Adding New Prompts
|
|
37
|
+
|
|
38
|
+
1. Create directory: `prompts/[purpose]/`
|
|
39
|
+
2. Create files:
|
|
40
|
+
- `types.ts` - Input data interface
|
|
41
|
+
- `sections.ts` - Reusable prompt fragments
|
|
42
|
+
- `index.ts` - Builder function + exports
|
|
43
|
+
3. Export from `prompts/index.ts`
|
|
44
|
+
4. Call from Processor with pre-fetched data
|
|
45
|
+
|
|
46
|
+
## Subdirectories
|
|
47
|
+
|
|
48
|
+
| Directory | Purpose | Key Output |
|
|
49
|
+
|-----------|---------|------------|
|
|
50
|
+
| `response/` | Generate persona replies | Conversational text |
|
|
51
|
+
| `human/` | Extract facts/traits/topics/people | JSON arrays |
|
|
52
|
+
| `persona/` | Extract persona traits, match topics | JSON |
|
|
53
|
+
| `generation/` | Create new personas | PersonaEntity JSON |
|
|
54
|
+
| `ceremony/` | Exposure decay prompts | Updated values |
|
|
55
|
+
| `heartbeat/` | Check if persona should speak | Boolean-ish |
|
|
56
|
+
| `validation/` | Verify extracted data | Confirmation |
|
|
57
|
+
|
|
58
|
+
## Key Insight
|
|
59
|
+
|
|
60
|
+
**Prompt engineering lives here. Code logic lives in Processor.**
|
|
61
|
+
|
|
62
|
+
When modifying persona behavior, check prompts first—the "personality" is in the English, not the TypeScript.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DescriptionCheckPromptData } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function buildDescriptionCheckPrompt(data: DescriptionCheckPromptData): { system: string; user: string } {
|
|
4
|
+
const traitList = data.traits.length > 0
|
|
5
|
+
? data.traits.map(t => `- ${t.name}: ${t.description}`).join("\n")
|
|
6
|
+
: "No traits defined";
|
|
7
|
+
|
|
8
|
+
const topicList = data.topics.length > 0
|
|
9
|
+
? data.topics.map(t => `- ${t.name}: ${t.perspective} (exposure: ${t.exposure_current.toFixed(2)})`).join("\n")
|
|
10
|
+
: "No topics defined";
|
|
11
|
+
|
|
12
|
+
const system = `You are evaluating whether a persona's description needs updating.
|
|
13
|
+
|
|
14
|
+
A description should ONLY be updated if there is a SIGNIFICANT mismatch between:
|
|
15
|
+
- The current description
|
|
16
|
+
- The persona's current traits and topics
|
|
17
|
+
|
|
18
|
+
Be VERY conservative. Only recommend updating if:
|
|
19
|
+
- The description mentions interests/traits that are completely absent from current data
|
|
20
|
+
- The persona has developed major new interests not reflected in the description
|
|
21
|
+
- The description actively contradicts the current personality
|
|
22
|
+
|
|
23
|
+
Do NOT recommend updating for:
|
|
24
|
+
- Minor differences or evolution
|
|
25
|
+
- Natural topic drift over time
|
|
26
|
+
- Missing details that aren't contradictions
|
|
27
|
+
|
|
28
|
+
Return JSON: { "should_update": true/false, "reason": "explanation" }`;
|
|
29
|
+
|
|
30
|
+
const user = `Persona: ${data.persona_name}
|
|
31
|
+
|
|
32
|
+
Current short description:
|
|
33
|
+
${data.current_short_description ?? "(none)"}
|
|
34
|
+
|
|
35
|
+
Current long description:
|
|
36
|
+
${data.current_long_description ?? "(none)"}
|
|
37
|
+
|
|
38
|
+
Current personality traits:
|
|
39
|
+
${traitList}
|
|
40
|
+
|
|
41
|
+
Current topics of interest:
|
|
42
|
+
${topicList}
|
|
43
|
+
|
|
44
|
+
Does this persona's description need updating based on their current traits and topics?`;
|
|
45
|
+
|
|
46
|
+
return { system, user };
|
|
47
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PersonaExpirePromptData } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function buildPersonaExpirePrompt(data: PersonaExpirePromptData): { system: string; user: string } {
|
|
4
|
+
const topicList = data.topics.map(t => {
|
|
5
|
+
const display = t.perspective || t.name;
|
|
6
|
+
return `- "${t.name}" (exposure: ${t.exposure_current.toFixed(2)}, sentiment: ${t.sentiment.toFixed(2)})\n Perspective: ${display}`;
|
|
7
|
+
}).join("\n");
|
|
8
|
+
|
|
9
|
+
const system = `You are evaluating which topics a persona should stop caring about.
|
|
10
|
+
|
|
11
|
+
A topic should be removed if:
|
|
12
|
+
- Its exposure_current is very low (< 0.15) indicating prolonged disinterest
|
|
13
|
+
- It no longer aligns with the persona's current interests
|
|
14
|
+
- It was a temporary interest that has faded
|
|
15
|
+
|
|
16
|
+
Be conservative - only suggest removing topics that are clearly irrelevant.
|
|
17
|
+
If unsure, keep the topic.
|
|
18
|
+
|
|
19
|
+
Return JSON: { "topic_ids_to_remove": ["id1", "id2"] }
|
|
20
|
+
Return empty array if no topics should be removed.`;
|
|
21
|
+
|
|
22
|
+
const user = `Persona: ${data.persona_name}
|
|
23
|
+
|
|
24
|
+
Current topics:
|
|
25
|
+
${topicList}
|
|
26
|
+
|
|
27
|
+
Which topics, if any, should this persona stop caring about?`;
|
|
28
|
+
|
|
29
|
+
return { system, user };
|
|
30
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { PersonaExplorePromptData } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function buildPersonaExplorePrompt(data: PersonaExplorePromptData): { system: string; user: string } {
|
|
4
|
+
const traitList = data.traits.map(t =>
|
|
5
|
+
`- ${t.name}: ${t.description} (strength: ${t.strength?.toFixed(2) ?? "N/A"})`
|
|
6
|
+
).join("\n");
|
|
7
|
+
|
|
8
|
+
const topicList = data.remaining_topics.map(t =>
|
|
9
|
+
`- ${t.name}: ${t.perspective || t.name}`
|
|
10
|
+
).join("\n");
|
|
11
|
+
|
|
12
|
+
const themeList = data.recent_conversation_themes.length > 0
|
|
13
|
+
? data.recent_conversation_themes.map(t => `- ${t}`).join("\n")
|
|
14
|
+
: "No recent themes identified";
|
|
15
|
+
|
|
16
|
+
const system = `You are generating new conversation topics for a persona.
|
|
17
|
+
|
|
18
|
+
Topics should:
|
|
19
|
+
- Align with the persona's traits and personality
|
|
20
|
+
- Complement (not duplicate) existing topics
|
|
21
|
+
- Be natural extensions of recent conversation themes
|
|
22
|
+
- Be specific enough to drive interesting conversations
|
|
23
|
+
|
|
24
|
+
Generate 1-3 new topics that this persona would genuinely care about.
|
|
25
|
+
|
|
26
|
+
Return JSON:
|
|
27
|
+
{
|
|
28
|
+
"new_topics": [{
|
|
29
|
+
"name": "Topic Name",
|
|
30
|
+
"perspective": "The persona's view or opinion on this topic",
|
|
31
|
+
"approach": "How they prefer to engage with this topic",
|
|
32
|
+
"personal_stake": "Why this topic matters to them personally",
|
|
33
|
+
"sentiment": 0.5,
|
|
34
|
+
"exposure_current": 0.2,
|
|
35
|
+
"exposure_desired": 0.6
|
|
36
|
+
}]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
**Field guidance:**
|
|
40
|
+
- perspective: REQUIRED - their actual view/opinion
|
|
41
|
+
- approach: Optional if unclear - how they discuss this topic
|
|
42
|
+
- personal_stake: Optional if unclear - why it matters to them
|
|
43
|
+
- exposure_current: Low (0.2) since these are new topics
|
|
44
|
+
- exposure_desired: How much the persona would want to discuss this`;
|
|
45
|
+
|
|
46
|
+
const user = `Persona: ${data.persona_name}
|
|
47
|
+
|
|
48
|
+
Personality traits:
|
|
49
|
+
${traitList}
|
|
50
|
+
|
|
51
|
+
Current topics (do not duplicate):
|
|
52
|
+
${topicList}
|
|
53
|
+
|
|
54
|
+
Recent conversation themes:
|
|
55
|
+
${themeList}
|
|
56
|
+
|
|
57
|
+
Generate new topics this persona would care about.`;
|
|
58
|
+
|
|
59
|
+
return { system, user };
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { buildPersonaExpirePrompt } from "./expire.js";
|
|
2
|
+
export { buildPersonaExplorePrompt } from "./explore.js";
|
|
3
|
+
export { buildDescriptionCheckPrompt } from "./description-check.js";
|
|
4
|
+
export type {
|
|
5
|
+
PersonaExpirePromptData,
|
|
6
|
+
PersonaExpireResult,
|
|
7
|
+
PersonaExplorePromptData,
|
|
8
|
+
PersonaExploreResult,
|
|
9
|
+
DescriptionCheckPromptData,
|
|
10
|
+
DescriptionCheckResult,
|
|
11
|
+
} from "./types.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Trait, PersonaTopic } from "../../core/types.js";
|
|
2
|
+
|
|
3
|
+
export interface PersonaExpirePromptData {
|
|
4
|
+
persona_name: string;
|
|
5
|
+
topics: PersonaTopic[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface PersonaExpireResult {
|
|
9
|
+
topic_ids_to_remove: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PersonaExplorePromptData {
|
|
13
|
+
persona_name: string;
|
|
14
|
+
traits: Trait[];
|
|
15
|
+
remaining_topics: PersonaTopic[];
|
|
16
|
+
recent_conversation_themes: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PersonaExploreResult {
|
|
20
|
+
new_topics: Array<{
|
|
21
|
+
name: string;
|
|
22
|
+
perspective: string;
|
|
23
|
+
approach: string;
|
|
24
|
+
personal_stake: string;
|
|
25
|
+
sentiment: number;
|
|
26
|
+
exposure_current: number;
|
|
27
|
+
exposure_desired: number;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DescriptionCheckPromptData {
|
|
32
|
+
persona_name: string;
|
|
33
|
+
current_short_description?: string;
|
|
34
|
+
current_long_description?: string;
|
|
35
|
+
traits: Trait[];
|
|
36
|
+
topics: PersonaTopic[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DescriptionCheckResult {
|
|
40
|
+
should_update: boolean;
|
|
41
|
+
reason?: string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { PersonaDescriptionsPromptData, PromptOutput } from "./types.js";
|
|
2
|
+
import type { Trait, PersonaTopic } from "../../core/types.js";
|
|
3
|
+
|
|
4
|
+
function formatTraitsForPrompt(traits: Trait[]): string {
|
|
5
|
+
if (traits.length === 0) return "(No traits defined)";
|
|
6
|
+
|
|
7
|
+
return traits.map(t => {
|
|
8
|
+
const strength = t.strength !== undefined ? ` (strength: ${t.strength.toFixed(1)})` : "";
|
|
9
|
+
return `- **${t.name}**${strength}: ${t.description}`;
|
|
10
|
+
}).join('\n');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function formatTopicsForPrompt(topics: PersonaTopic[]): string {
|
|
14
|
+
if (topics.length === 0) return "(No topics defined)";
|
|
15
|
+
|
|
16
|
+
return topics.map(t => {
|
|
17
|
+
const sentiment = t.sentiment > 0.3 ? "enjoys" : t.sentiment < -0.3 ? "dislikes" : "neutral";
|
|
18
|
+
return `- **${t.name}** (${sentiment}): ${t.perspective || t.name}`;
|
|
19
|
+
}).join('\n');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildPersonaDescriptionsPrompt(data: PersonaDescriptionsPromptData): PromptOutput {
|
|
23
|
+
if (!data.name) {
|
|
24
|
+
throw new Error("buildPersonaDescriptionsPrompt: name is required");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const taskFragment = `You are regenerating descriptions for an existing AI persona named "${data.name}".
|
|
28
|
+
|
|
29
|
+
Their traits and topics have evolved, and the descriptions may no longer accurately represent who they are.
|
|
30
|
+
|
|
31
|
+
**Important**: Only change descriptions if there's a significant mismatch. If the current descriptions still fit, return \`{ "no_change": true }\`.`;
|
|
32
|
+
|
|
33
|
+
const currentStateFragment = `## Current State
|
|
34
|
+
|
|
35
|
+
### Aliases
|
|
36
|
+
${data.aliases.length > 0 ? data.aliases.join(", ") : "(None)"}
|
|
37
|
+
|
|
38
|
+
### Traits
|
|
39
|
+
${formatTraitsForPrompt(data.traits)}
|
|
40
|
+
|
|
41
|
+
### Topics
|
|
42
|
+
${formatTopicsForPrompt(data.topics)}`;
|
|
43
|
+
|
|
44
|
+
const guidelinesFragment = `## Guidelines
|
|
45
|
+
|
|
46
|
+
**When to change:**
|
|
47
|
+
- Traits/topics have shifted significantly from the original concept
|
|
48
|
+
- Current descriptions mention things that are no longer true
|
|
49
|
+
- The persona's core identity has evolved
|
|
50
|
+
|
|
51
|
+
**When NOT to change:**
|
|
52
|
+
- Descriptions are still accurate even if incomplete
|
|
53
|
+
- Changes are minor refinements
|
|
54
|
+
- Original descriptions capture the essence well
|
|
55
|
+
|
|
56
|
+
**If changing:**
|
|
57
|
+
- short_description: 10-15 words capturing the essence
|
|
58
|
+
- long_description: 2-3 sentences describing personality, interests, approach
|
|
59
|
+
- Preserve the persona's core identity while reflecting evolution`;
|
|
60
|
+
|
|
61
|
+
const schemaFragment = `## Response Format
|
|
62
|
+
|
|
63
|
+
If descriptions should change:
|
|
64
|
+
\`\`\`json
|
|
65
|
+
{
|
|
66
|
+
"short_description": "New short description here",
|
|
67
|
+
"long_description": "New long description here."
|
|
68
|
+
}
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
If descriptions are still accurate:
|
|
72
|
+
\`\`\`json
|
|
73
|
+
{
|
|
74
|
+
"no_change": true
|
|
75
|
+
}
|
|
76
|
+
\`\`\``;
|
|
77
|
+
|
|
78
|
+
const system = `${taskFragment}
|
|
79
|
+
|
|
80
|
+
${currentStateFragment}
|
|
81
|
+
|
|
82
|
+
${guidelinesFragment}
|
|
83
|
+
|
|
84
|
+
${schemaFragment}`;
|
|
85
|
+
|
|
86
|
+
const user = `Based on the traits and topics above, should ${data.name}'s descriptions be updated?
|
|
87
|
+
|
|
88
|
+
Remember: Only change if there's a significant mismatch. Stability is preferred.`;
|
|
89
|
+
|
|
90
|
+
return { system, user };
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { buildPersonaGenerationPrompt } from "./persona.js";
|
|
2
|
+
export { buildPersonaDescriptionsPrompt } from "./descriptions.js";
|
|
3
|
+
export {
|
|
4
|
+
DEFAULT_SEED_TRAITS,
|
|
5
|
+
SEED_TRAIT_GENUINE,
|
|
6
|
+
SEED_TRAIT_NATURAL_SPEECH,
|
|
7
|
+
type SeedTrait,
|
|
8
|
+
} from "./seeds.js";
|
|
9
|
+
export type {
|
|
10
|
+
PersonaGenerationPromptData,
|
|
11
|
+
PersonaGenerationResult,
|
|
12
|
+
PersonaDescriptionsPromptData,
|
|
13
|
+
PersonaDescriptionsResult,
|
|
14
|
+
PromptOutput,
|
|
15
|
+
} from "./types.js";
|