ei-tui 0.9.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/package.json +8 -1
- package/src/README.md +10 -26
- package/src/core/context-utils.ts +2 -2
- package/src/core/handlers/document-segmentation.ts +113 -0
- package/src/core/handlers/heartbeat.ts +9 -1
- package/src/core/handlers/human-extraction.ts +4 -1
- package/src/core/handlers/human-matching.ts +5 -53
- package/src/core/handlers/index.ts +3 -51
- package/src/core/handlers/persona-generation.ts +1 -28
- package/src/core/handlers/rewrite.ts +13 -9
- package/src/core/handlers/utils.ts +2 -9
- package/src/core/heartbeat-manager.ts +5 -5
- package/src/core/llm-client.ts +11 -1
- package/src/core/message-manager.ts +26 -23
- package/src/core/orchestrators/ceremony.ts +87 -49
- package/src/core/orchestrators/extraction-chunker.ts +3 -3
- package/src/core/orchestrators/human-extraction.ts +22 -18
- package/src/core/orchestrators/index.ts +0 -1
- package/src/core/orchestrators/persona-topics.ts +1 -1
- package/src/core/orchestrators/room-extraction.ts +5 -5
- package/src/core/persona-manager.ts +4 -0
- package/src/core/processor.ts +98 -22
- package/src/core/prompt-context-builder.ts +7 -6
- package/src/core/queue-manager.ts +35 -0
- package/src/core/state/personas.ts +1 -17
- package/src/core/state/queue.ts +9 -1
- package/src/core/state-manager.ts +4 -66
- package/src/core/types/entities.ts +17 -3
- package/src/core/types/enums.ts +1 -2
- package/src/core/types/integrations.ts +2 -0
- package/src/core/types/llm.ts +9 -0
- package/src/core/types/rooms.ts +1 -1
- package/src/integrations/claude-code/importer.ts +1 -1
- package/src/integrations/cursor/importer.ts +1 -1
- package/src/integrations/document/chunker.ts +88 -0
- package/src/integrations/document/importer.ts +82 -0
- package/src/integrations/document/index.ts +2 -0
- package/src/integrations/document/invoice.ts +63 -0
- package/src/integrations/document/types.ts +16 -0
- package/src/integrations/document/unsource.ts +164 -0
- package/src/integrations/opencode/importer.ts +1 -1
- package/src/integrations/persona-history/importer.ts +197 -0
- package/src/integrations/persona-history/index.ts +3 -0
- package/src/integrations/persona-history/types.ts +7 -0
- package/src/prompts/ceremony/dedup.ts +7 -3
- package/src/prompts/ceremony/index.ts +2 -11
- package/src/prompts/ceremony/people-rewrite.ts +190 -0
- package/src/prompts/ceremony/{rewrite.ts → topic-rewrite.ts} +103 -78
- package/src/prompts/ceremony/types.ts +1 -42
- package/src/prompts/generation/index.ts +0 -3
- package/src/prompts/generation/types.ts +0 -15
- package/src/prompts/heartbeat/check.ts +18 -6
- package/src/prompts/heartbeat/types.ts +2 -1
- package/src/prompts/human/index.ts +0 -2
- package/src/prompts/human/person-scan.ts +13 -4
- package/src/prompts/human/topic-scan.ts +16 -2
- package/src/prompts/human/topic-update.ts +36 -4
- package/src/prompts/human/types.ts +1 -16
- package/src/prompts/index.ts +0 -19
- package/src/prompts/reflection/index.ts +35 -5
- package/src/prompts/reflection/types.ts +1 -1
- package/src/prompts/response/index.ts +5 -0
- package/src/prompts/response/sections.ts +26 -0
- package/src/prompts/response/types.ts +3 -0
- package/src/storage/indexed.ts +4 -0
- package/src/storage/interface.ts +1 -0
- package/src/storage/local.ts +4 -0
- package/src/templates/emmett.ts +49 -0
- package/tui/README.md +22 -0
- package/tui/src/app.tsx +9 -6
- package/tui/src/commands/delete.tsx +7 -1
- package/tui/src/commands/import.tsx +30 -0
- package/tui/src/commands/registry.test.ts +10 -5
- package/tui/src/commands/unsource.tsx +115 -0
- package/tui/src/components/PromptInput.tsx +4 -0
- package/tui/src/components/WelcomeOverlay.tsx +58 -32
- package/tui/src/context/ei.tsx +80 -60
- package/tui/src/globals.d.ts +57 -0
- package/tui/src/index.tsx +14 -0
- package/tui/src/storage/file.ts +11 -5
- package/tui/src/util/e2e-flags.ts +4 -3
- package/tui/src/util/help-content.ts +20 -0
- package/tui/src/util/provider-detection.ts +251 -0
- package/tui/src/util/yaml-human.ts +7 -1
- package/tui/src/util/yaml-persona.ts +8 -4
- package/tui/src/util/yaml-settings.ts +3 -3
- package/src/core/orchestrators/person-migration.ts +0 -55
- package/src/prompts/ceremony/description-check.ts +0 -54
- package/src/prompts/ceremony/expire.ts +0 -37
- package/src/prompts/ceremony/explore.ts +0 -77
- package/src/prompts/ceremony/person-migration.ts +0 -77
- package/src/prompts/generation/descriptions.ts +0 -91
- package/src/prompts/human/fact-scan.ts +0 -150
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { ProviderType } from "../../../src/core/types.js";
|
|
2
|
+
import type { ProviderAccount, ModelConfig } from "../../../src/core/types.js";
|
|
3
|
+
|
|
4
|
+
export interface LocalProviderConfig {
|
|
5
|
+
name: string;
|
|
6
|
+
url: string;
|
|
7
|
+
priority: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CloudProviderConfig {
|
|
11
|
+
name: string;
|
|
12
|
+
envVar: string;
|
|
13
|
+
url: string;
|
|
14
|
+
priority: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SelectedModels {
|
|
18
|
+
extractionModel: string;
|
|
19
|
+
chatModel: string;
|
|
20
|
+
bonusModel?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ProviderDetectionResult {
|
|
24
|
+
name: string;
|
|
25
|
+
url: string;
|
|
26
|
+
apiKey?: string;
|
|
27
|
+
modelIds: string[];
|
|
28
|
+
selected: SelectedModels;
|
|
29
|
+
status: "detected" | "failed";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ProviderDetectionStatus {
|
|
33
|
+
name: string;
|
|
34
|
+
detected: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DetectProvidersOptions {
|
|
38
|
+
skipLocalDetect?: boolean;
|
|
39
|
+
skipCloudDetect?: boolean;
|
|
40
|
+
env?: Record<string, string | undefined>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const LOCAL_PROVIDERS: ReadonlyArray<LocalProviderConfig> = [
|
|
44
|
+
{ name: "LMStudio", url: "http://127.0.0.1:1234/v1", priority: 1 },
|
|
45
|
+
{ name: "Ollama", url: "http://127.0.0.1:11434/v1", priority: 2 },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export const CLOUD_PROVIDERS: ReadonlyArray<CloudProviderConfig> = [
|
|
49
|
+
{ name: "Anthropic", envVar: "ANTHROPIC_API_KEY", url: "https://api.anthropic.com/v1", priority: 3 },
|
|
50
|
+
{ name: "OpenAI", envVar: "OPENAI_API_KEY", url: "https://api.openai.com/v1", priority: 4 },
|
|
51
|
+
{ name: "Groq", envVar: "GROQ_API_KEY", url: "https://api.groq.com/openai/v1", priority: 5 },
|
|
52
|
+
{ name: "Mistral", envVar: "MISTRAL_API_KEY", url: "https://api.mistral.ai/v1", priority: 6 },
|
|
53
|
+
{ name: "Gemini", envVar: "GEMINI_API_KEY", url: "https://generativelanguage.googleapis.com/v1beta/openai", priority: 7 },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
export const ALL_PROVIDER_NAMES: ReadonlyArray<string> = [
|
|
57
|
+
...LOCAL_PROVIDERS.map((p) => p.name),
|
|
58
|
+
...CLOUD_PROVIDERS.map((p) => p.name),
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
function latestMatch(modelIds: string[], pattern: string): string | undefined {
|
|
62
|
+
const matches = modelIds.filter((id) => id.toLowerCase().includes(pattern));
|
|
63
|
+
if (matches.length === 0) return undefined;
|
|
64
|
+
return [...matches].sort().reverse()[0];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function selectModelsForProvider(
|
|
68
|
+
providerName: string,
|
|
69
|
+
modelIds: string[]
|
|
70
|
+
): SelectedModels {
|
|
71
|
+
const name = providerName.toLowerCase();
|
|
72
|
+
|
|
73
|
+
if (name === "groq") {
|
|
74
|
+
return {
|
|
75
|
+
extractionModel: "llama-3.1-8b-instant",
|
|
76
|
+
chatModel: "llama-3.3-70b-versatile",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (modelIds.length === 0) {
|
|
81
|
+
return { extractionModel: "default", chatModel: "default" };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (name === "anthropic") {
|
|
85
|
+
return {
|
|
86
|
+
extractionModel: latestMatch(modelIds, "haiku") ?? modelIds[0],
|
|
87
|
+
chatModel: latestMatch(modelIds, "sonnet") ?? modelIds[0],
|
|
88
|
+
bonusModel: latestMatch(modelIds, "opus"),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (name === "openai") {
|
|
93
|
+
const gpt4oNonMini = modelIds.filter(
|
|
94
|
+
(id) => id.toLowerCase().includes("gpt-4o") && !id.toLowerCase().includes("mini")
|
|
95
|
+
);
|
|
96
|
+
return {
|
|
97
|
+
extractionModel: latestMatch(modelIds, "mini") ?? modelIds[0],
|
|
98
|
+
chatModel: gpt4oNonMini.length > 0
|
|
99
|
+
? [...gpt4oNonMini].sort().reverse()[0]
|
|
100
|
+
: modelIds[0],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (name === "mistral") {
|
|
105
|
+
return {
|
|
106
|
+
extractionModel: latestMatch(modelIds, "small") ?? modelIds[0],
|
|
107
|
+
chatModel: latestMatch(modelIds, "large") ?? modelIds[0],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (name === "gemini") {
|
|
112
|
+
return {
|
|
113
|
+
extractionModel: latestMatch(modelIds, "flash") ?? modelIds[0],
|
|
114
|
+
chatModel: latestMatch(modelIds, "pro") ?? modelIds[0],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { extractionModel: modelIds[0], chatModel: modelIds[0] };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;
|
|
122
|
+
|
|
123
|
+
function buildAuthHeaders(url: string, apiKey: string | undefined): Record<string, string> {
|
|
124
|
+
if (!apiKey) return {};
|
|
125
|
+
if (url.includes("api.anthropic.com")) {
|
|
126
|
+
return {
|
|
127
|
+
"x-api-key": apiKey,
|
|
128
|
+
"anthropic-version": "2023-06-01",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return { "Authorization": `Bearer ${apiKey}` };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function probeModels(
|
|
135
|
+
url: string,
|
|
136
|
+
apiKey: string | undefined,
|
|
137
|
+
fetchFn: FetchFn
|
|
138
|
+
): Promise<string[] | null> {
|
|
139
|
+
try {
|
|
140
|
+
const headers = buildAuthHeaders(url, apiKey);
|
|
141
|
+
const response = await fetchFn(`${url}/models`, {
|
|
142
|
+
method: "GET",
|
|
143
|
+
headers,
|
|
144
|
+
signal: AbortSignal.timeout(3000),
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) return null;
|
|
147
|
+
const json = await response.json() as { data?: Array<{ id: string }> };
|
|
148
|
+
return (json.data ?? []).map((m) => m.id).filter(Boolean);
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function detectProviders(
|
|
155
|
+
options: DetectProvidersOptions = {},
|
|
156
|
+
fetchFn: FetchFn = fetch
|
|
157
|
+
): Promise<{
|
|
158
|
+
detected: ProviderDetectionResult[];
|
|
159
|
+
statuses: ProviderDetectionStatus[];
|
|
160
|
+
}> {
|
|
161
|
+
const env = options.env ?? (process.env as Record<string, string | undefined>);
|
|
162
|
+
const detected: ProviderDetectionResult[] = [];
|
|
163
|
+
const statuses: ProviderDetectionStatus[] = [];
|
|
164
|
+
|
|
165
|
+
const localResults = await Promise.all(
|
|
166
|
+
LOCAL_PROVIDERS.map(async (provider) => {
|
|
167
|
+
if (options.skipLocalDetect) return { provider, modelIds: null };
|
|
168
|
+
const modelIds = await probeModels(provider.url, undefined, fetchFn);
|
|
169
|
+
return { provider, modelIds };
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
for (const { provider, modelIds } of localResults) {
|
|
174
|
+
const ok = modelIds !== null;
|
|
175
|
+
statuses.push({ name: provider.name, detected: ok });
|
|
176
|
+
if (ok) {
|
|
177
|
+
detected.push({
|
|
178
|
+
name: provider.name,
|
|
179
|
+
url: provider.url,
|
|
180
|
+
modelIds: modelIds!,
|
|
181
|
+
selected: selectModelsForProvider(provider.name, modelIds!),
|
|
182
|
+
status: "detected",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const cloudResults = await Promise.all(
|
|
188
|
+
CLOUD_PROVIDERS.map(async (provider) => {
|
|
189
|
+
const apiKey = env[provider.envVar];
|
|
190
|
+
if (!apiKey) return { provider, apiKey: undefined, modelIds: null };
|
|
191
|
+
if (options.skipCloudDetect) return { provider, apiKey, modelIds: null };
|
|
192
|
+
const modelIds = await probeModels(provider.url, apiKey, fetchFn);
|
|
193
|
+
return { provider, apiKey, modelIds };
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
for (const { provider, apiKey, modelIds } of cloudResults) {
|
|
198
|
+
const ok = modelIds !== null;
|
|
199
|
+
statuses.push({ name: provider.name, detected: ok });
|
|
200
|
+
if (ok) {
|
|
201
|
+
detected.push({
|
|
202
|
+
name: provider.name,
|
|
203
|
+
url: provider.url,
|
|
204
|
+
apiKey,
|
|
205
|
+
modelIds: modelIds!,
|
|
206
|
+
selected: selectModelsForProvider(provider.name, modelIds!),
|
|
207
|
+
status: "detected",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { detected, statuses };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function buildProviderAccounts(
|
|
216
|
+
detected: ProviderDetectionResult[]
|
|
217
|
+
): ProviderAccount[] {
|
|
218
|
+
return detected.map((d) => {
|
|
219
|
+
const makeModel = (modelName: string): ModelConfig => ({
|
|
220
|
+
id: crypto.randomUUID(),
|
|
221
|
+
name: modelName,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const seenNames = new Set<string>();
|
|
225
|
+
const models: ModelConfig[] = [];
|
|
226
|
+
|
|
227
|
+
const pushIfNew = (name: string) => {
|
|
228
|
+
if (!seenNames.has(name)) {
|
|
229
|
+
seenNames.add(name);
|
|
230
|
+
models.push(makeModel(name));
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
pushIfNew(d.selected.chatModel);
|
|
235
|
+
pushIfNew(d.selected.extractionModel);
|
|
236
|
+
if (d.selected.bonusModel) pushIfNew(d.selected.bonusModel);
|
|
237
|
+
for (const id of d.modelIds) pushIfNew(id);
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
id: crypto.randomUUID(),
|
|
241
|
+
name: d.name,
|
|
242
|
+
type: "llm" as ProviderType,
|
|
243
|
+
url: d.url,
|
|
244
|
+
api_key: d.apiKey,
|
|
245
|
+
enabled: true,
|
|
246
|
+
created_at: new Date().toISOString(),
|
|
247
|
+
default_model: d.selected.chatModel,
|
|
248
|
+
models,
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
}
|
|
@@ -191,6 +191,12 @@ export function humanToYAML(
|
|
|
191
191
|
})
|
|
192
192
|
.replace(/^(\s+)(identifiers:)/mg, (_, indent, _key) => {
|
|
193
193
|
return `${indent}${personComment}\n${indent}identifiers:`;
|
|
194
|
+
})
|
|
195
|
+
.replace(/^( +)(sources:\n(?:\1 - .+\n)*)/mg, (match, indent, block) => {
|
|
196
|
+
return block
|
|
197
|
+
.split('\n')
|
|
198
|
+
.map(line => line.trim() ? `${indent}# [read-only] ${line}` : line)
|
|
199
|
+
.join('\n');
|
|
194
200
|
});
|
|
195
201
|
|
|
196
202
|
const serializeSection = (key: "facts" | "topics" | "people", items: unknown[] | undefined): string => {
|
|
@@ -199,7 +205,7 @@ export function humanToYAML(
|
|
|
199
205
|
return `${key}:\n${stub}`;
|
|
200
206
|
}
|
|
201
207
|
const ordered = (items as object[]).map(canonicalFieldOrder);
|
|
202
|
-
const itemsYaml = YAML.stringify(ordered, { lineWidth: 0 })
|
|
208
|
+
const itemsYaml = YAML.stringify(ordered, { lineWidth: 0, aliasDuplicateObjects: false })
|
|
203
209
|
.split('\n')
|
|
204
210
|
.map(line => ` ${line}`)
|
|
205
211
|
.join('\n')
|
|
@@ -38,7 +38,7 @@ interface EditablePersonaData {
|
|
|
38
38
|
traits: YAMLTrait[];
|
|
39
39
|
topics: YAMLPersonaTopic[];
|
|
40
40
|
heartbeat_delay_ms?: string | null;
|
|
41
|
-
|
|
41
|
+
context_window_ms?: string | null;
|
|
42
42
|
is_paused?: boolean;
|
|
43
43
|
pause_until?: string;
|
|
44
44
|
is_static?: boolean;
|
|
@@ -179,7 +179,9 @@ export function newPersonaFromYAML(yamlContent: string, allTools?: ToolDefinitio
|
|
|
179
179
|
heartbeat_delay_ms: data.heartbeat_delay_ms == null
|
|
180
180
|
? undefined
|
|
181
181
|
: parseDuration(data.heartbeat_delay_ms) ?? undefined,
|
|
182
|
-
|
|
182
|
+
context_window_ms: data.context_window_ms == null
|
|
183
|
+
? undefined
|
|
184
|
+
: parseDuration(data.context_window_ms) ?? undefined,
|
|
183
185
|
tools: resolvePersonaToolsFromMap(data.tools, allTools ?? [], allProviders ?? []),
|
|
184
186
|
};
|
|
185
187
|
}
|
|
@@ -218,7 +220,7 @@ export function personaToYAML(persona: PersonaEntity, allGroups?: string[], allT
|
|
|
218
220
|
name, perspective, approach, personal_stake, sentiment: sentiment ?? 0, exposure_current, exposure_desired
|
|
219
221
|
})),
|
|
220
222
|
heartbeat_delay_ms: persona.heartbeat_delay_ms ? formatDuration(persona.heartbeat_delay_ms) : null,
|
|
221
|
-
|
|
223
|
+
context_window_ms: persona.context_window_ms ? formatDuration(persona.context_window_ms) : null,
|
|
222
224
|
is_paused: persona.is_paused || undefined,
|
|
223
225
|
pause_until: persona.pause_until,
|
|
224
226
|
is_static: persona.is_static || undefined,
|
|
@@ -328,7 +330,9 @@ export function personaFromYAML(yamlContent: string, original: PersonaEntity, al
|
|
|
328
330
|
heartbeat_delay_ms: data.heartbeat_delay_ms == null
|
|
329
331
|
? undefined
|
|
330
332
|
: parseDuration(data.heartbeat_delay_ms) ?? undefined,
|
|
331
|
-
|
|
333
|
+
context_window_ms: data.context_window_ms == null
|
|
334
|
+
? undefined
|
|
335
|
+
: parseDuration(data.context_window_ms) ?? undefined,
|
|
332
336
|
is_paused: data.is_paused ?? false,
|
|
333
337
|
pause_until: data.pause_until,
|
|
334
338
|
is_static: data.is_static ?? false,
|
|
@@ -16,7 +16,7 @@ interface EditableSettingsData {
|
|
|
16
16
|
rewrite_model?: string | null;
|
|
17
17
|
name_display?: string | null;
|
|
18
18
|
default_heartbeat_ms?: string | null;
|
|
19
|
-
|
|
19
|
+
default_context_window_ms?: string | null;
|
|
20
20
|
message_min_count?: number | null;
|
|
21
21
|
message_max_age_days?: number | null;
|
|
22
22
|
ceremony?: {
|
|
@@ -65,7 +65,7 @@ export function settingsToYAML(settings: HumanSettings | undefined, accounts: Pr
|
|
|
65
65
|
rewrite_model: guidToDisplay(settings?.rewrite_model),
|
|
66
66
|
name_display: settings?.name_display ?? null,
|
|
67
67
|
default_heartbeat_ms: formatDuration(settings?.default_heartbeat_ms ?? 1800000),
|
|
68
|
-
|
|
68
|
+
default_context_window_ms: formatDuration(settings?.default_context_window_ms ?? 28800000),
|
|
69
69
|
message_min_count: settings?.message_min_count ?? 200,
|
|
70
70
|
message_max_age_days: settings?.message_max_age_days ?? 14,
|
|
71
71
|
ceremony: {
|
|
@@ -190,7 +190,7 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
|
|
|
190
190
|
rewrite_model: displayToGuid(data.rewrite_model),
|
|
191
191
|
name_display: nullToUndefined(data.name_display),
|
|
192
192
|
default_heartbeat_ms: parseMsDuration(data.default_heartbeat_ms, 1800000),
|
|
193
|
-
|
|
193
|
+
default_context_window_ms: parseMsDuration(data.default_context_window_ms, 28800000),
|
|
194
194
|
message_min_count: nullToUndefined(data.message_min_count),
|
|
195
195
|
message_max_age_days: nullToUndefined(data.message_max_age_days),
|
|
196
196
|
ceremony,
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { LLMRequestType, LLMPriority, LLMNextStep } from "../types.js";
|
|
2
|
-
import type { StateManager } from "../state-manager.js";
|
|
3
|
-
import { buildPersonMigrationPrompt } from "../../prompts/ceremony/index.js";
|
|
4
|
-
|
|
5
|
-
export function queuePersonMigration(state: StateManager): void {
|
|
6
|
-
const human = state.getHuman();
|
|
7
|
-
|
|
8
|
-
if (human.settings?.people_migration_complete) {
|
|
9
|
-
console.log("[PersonMigration] Migration complete flag set — skipping");
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const unmigrated = human.people.filter(p => !p.identifiers || p.identifiers.length === 0);
|
|
14
|
-
|
|
15
|
-
if (unmigrated.length === 0) {
|
|
16
|
-
console.log("[PersonMigration] All Person records have identifiers — marking migration complete");
|
|
17
|
-
state.setHuman({
|
|
18
|
-
...human,
|
|
19
|
-
settings: {
|
|
20
|
-
...human.settings,
|
|
21
|
-
people_migration_complete: true,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
console.log(`[PersonMigration] Queuing migration for ${unmigrated.length} Person record(s)`);
|
|
28
|
-
|
|
29
|
-
const rewriteModel = human.settings?.rewrite_model;
|
|
30
|
-
|
|
31
|
-
for (const person of unmigrated) {
|
|
32
|
-
const prompt = buildPersonMigrationPrompt({
|
|
33
|
-
person: {
|
|
34
|
-
name: person.name,
|
|
35
|
-
description: person.description,
|
|
36
|
-
relationship: person.relationship,
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
state.queue_enqueue({
|
|
41
|
-
type: LLMRequestType.JSON,
|
|
42
|
-
priority: LLMPriority.Normal,
|
|
43
|
-
system: prompt.system,
|
|
44
|
-
user: prompt.user,
|
|
45
|
-
next_step: LLMNextStep.HandlePersonIdentifierMigration,
|
|
46
|
-
...(rewriteModel ? { model: rewriteModel } : {}),
|
|
47
|
-
data: {
|
|
48
|
-
person_id: person.id,
|
|
49
|
-
ceremony_progress: 1,
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
console.log(`[PersonMigration] Queued ${unmigrated.length} migration request(s)`);
|
|
55
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
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 JSON:**
|
|
47
|
-
\`\`\`json
|
|
48
|
-
{ "should_update": true, "reason": "explanation" }
|
|
49
|
-
\`\`\`
|
|
50
|
-
|
|
51
|
-
If no update is needed: \`{ "should_update": false, "reason": "explanation" }\``;
|
|
52
|
-
|
|
53
|
-
return { system, user };
|
|
54
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
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 JSON:**
|
|
30
|
-
\`\`\`json
|
|
31
|
-
{ "topic_ids_to_remove": ["id1", "id2"] }
|
|
32
|
-
\`\`\`
|
|
33
|
-
|
|
34
|
-
Return an empty array if no topics should be removed.`;
|
|
35
|
-
|
|
36
|
-
return { system, user };
|
|
37
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
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 JSON:**
|
|
60
|
-
\`\`\`json
|
|
61
|
-
{
|
|
62
|
-
"new_topics": [
|
|
63
|
-
{
|
|
64
|
-
"name": "Topic Name",
|
|
65
|
-
"perspective": "Their view or opinion",
|
|
66
|
-
"approach": "How they engage with it",
|
|
67
|
-
"personal_stake": "Why it matters to them",
|
|
68
|
-
"sentiment": 0.5,
|
|
69
|
-
"exposure_current": 0.2,
|
|
70
|
-
"exposure_desired": 0.6
|
|
71
|
-
}
|
|
72
|
-
]
|
|
73
|
-
}
|
|
74
|
-
\`\`\``;
|
|
75
|
-
|
|
76
|
-
return { system, user };
|
|
77
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import type { Person } from "../../core/types/data-items.js";
|
|
2
|
-
import type { PromptOutput } from "../persona/types.js";
|
|
3
|
-
|
|
4
|
-
export interface PersonMigrationPromptData {
|
|
5
|
-
person: Pick<Person, "name" | "description" | "relationship">;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function buildPersonMigrationPrompt(data: PersonMigrationPromptData): PromptOutput {
|
|
9
|
-
const { person } = data;
|
|
10
|
-
|
|
11
|
-
const system = `You are extracting identifiers for a Person record in a personal knowledge system.
|
|
12
|
-
|
|
13
|
-
A Person record has a \`name\` and \`description\` that describe who they are. Your job is to extract ALL identifiers for this person — every name, handle, alias, or platform ID that refers to them.
|
|
14
|
-
|
|
15
|
-
## CRITICAL RULES
|
|
16
|
-
|
|
17
|
-
1. The \`name\` field value MUST appear in identifiers — never lose it.
|
|
18
|
-
2. If \`name\` contains a space (e.g. "Jeremy Scherer"), create BOTH a \`Full Name\` identifier AND a \`First Name\` identifier for the given name.
|
|
19
|
-
3. Mark exactly one identifier as \`is_primary: true\` — the most natural display name.
|
|
20
|
-
4. Check \`read_memory\` for additional context before finalizing.
|
|
21
|
-
5. Return ONLY a JSON object with an \`identifiers\` array. No other text.
|
|
22
|
-
6. "none" is never valid — every person has at least one identifier (their name).
|
|
23
|
-
|
|
24
|
-
## DESCRIPTION PRE-PROCESSING
|
|
25
|
-
|
|
26
|
-
If the description begins with a JSON block (e.g. \`{"identifiers": [...]}\` or \`[{...}]\`), those are pre-seeded identifiers from a prior migration step:
|
|
27
|
-
- Parse them out and include them in your output
|
|
28
|
-
- Normalize their \`type\` values to Title Case (e.g. \`nickname\` → \`Nickname\`, \`full_name\` → \`Full Name\`)
|
|
29
|
-
- Treat the remainder of the description (after the JSON block) as the actual description text for \`read_memory\` context
|
|
30
|
-
|
|
31
|
-
## IDENTIFIER TYPES
|
|
32
|
-
|
|
33
|
-
Use Title Case for all types. Built-in types:
|
|
34
|
-
|
|
35
|
-
| Type | Meaning |
|
|
36
|
-
|------|---------|
|
|
37
|
-
| Full Name | Legal or full birth name |
|
|
38
|
-
| First Name | Given/first name only |
|
|
39
|
-
| Nickname | Informal name, diminutive, pet name |
|
|
40
|
-
| Email | Email address |
|
|
41
|
-
| GitHub | GitHub username |
|
|
42
|
-
| Discord | Discord username |
|
|
43
|
-
| Roblox | Roblox username |
|
|
44
|
-
| Reddit | Reddit username |
|
|
45
|
-
| Twitter | Twitter/X handle |
|
|
46
|
-
| FF14 | Final Fantasy XIV character name |
|
|
47
|
-
| Relationship | How the user addresses this person: Dad, Pop, Sis, etc. |
|
|
48
|
-
| Ei Persona | Ei persona UUID (only if explicitly in the data) |
|
|
49
|
-
|
|
50
|
-
Any string is valid as a type — users define their own (e.g. \`Slack-ASU\`, \`Slack-RnP\`, \`sehimu_thinara\`). Use the exact casing the user would recognize.
|
|
51
|
-
|
|
52
|
-
## RESPONSE FORMAT
|
|
53
|
-
|
|
54
|
-
\`\`\`json
|
|
55
|
-
{
|
|
56
|
-
"identifiers": [
|
|
57
|
-
{ "type": "Nickname", "value": "Flare", "is_primary": true },
|
|
58
|
-
{ "type": "Full Name", "value": "Jeremy Scherer" },
|
|
59
|
-
{ "type": "First Name", "value": "Jeremy" },
|
|
60
|
-
{ "type": "GitHub", "value": "Flare576" },
|
|
61
|
-
{ "type": "Slack-RnP", "value": "jeremy.scherer" }
|
|
62
|
-
]
|
|
63
|
-
}
|
|
64
|
-
\`\`\``;
|
|
65
|
-
|
|
66
|
-
const user = `Extract identifiers for this Person record.
|
|
67
|
-
|
|
68
|
-
Name: ${person.name}
|
|
69
|
-
Relationship: ${person.relationship ?? "unknown"}
|
|
70
|
-
Description: ${person.description ?? "(none)"}
|
|
71
|
-
|
|
72
|
-
First, call read_memory to search for any additional context about "${person.name}". Then return the complete identifiers array.
|
|
73
|
-
|
|
74
|
-
Return JSON only.`;
|
|
75
|
-
|
|
76
|
-
return { system, user };
|
|
77
|
-
}
|