ei-tui 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -6
- package/package.json +1 -1
- package/src/core/handlers/persona-generation.ts +37 -9
- package/src/core/heartbeat-manager.ts +3 -5
- package/src/core/message-manager.ts +10 -11
- package/src/core/orchestrators/ceremony.ts +7 -4
- package/src/core/processor.ts +27 -6
- package/src/core/types/entities.ts +4 -4
- package/src/prompts/persona/traits.ts +19 -5
- package/src/prompts/persona/types.ts +1 -0
- package/tui/README.md +15 -1
- package/tui/src/util/yaml-serializers.ts +20 -3
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Ei
|
|
2
2
|
|
|
3
|
-
A local-first AI companion system with persistent personas and
|
|
3
|
+
A local-first AI companion system with persistent personas and coding tool integrations (OpenCode, Claude Code, Cursor).
|
|
4
4
|
|
|
5
5
|
You can access the Web version at [ei.flare576.com](https://ei.flare576.com).
|
|
6
6
|
|
|
7
7
|
You can install the local version via `npm install -g ei-tui` (see [### TUI](#tui) for details).
|
|
8
8
|
|
|
9
|
-
If you're here to give
|
|
9
|
+
If you're here to give your coding tools (OpenCode, Claude Code, Cursor) persistent memory, jump over to [TUI README.md](./tui/README.md) to learn how to get information _into_ Ei, and [CLI README.md](./src/cli/README.md) to get it back _out_.
|
|
10
10
|
|
|
11
11
|
## What Does "Local First" Mean?
|
|
12
12
|
|
|
@@ -108,13 +108,49 @@ Regardless, Running `ei` pops open the TUI interface and, just like on the web,
|
|
|
108
108
|
|
|
109
109
|
More information (including commands) can be found in the [TUI Readme](tui/README.md)
|
|
110
110
|
|
|
111
|
-
###
|
|
111
|
+
### Coding Tool Integrations
|
|
112
112
|
|
|
113
|
-
Ei
|
|
113
|
+
Ei can import sessions from your coding tools and extract what you've been working on — pulling out facts, topics, and context that persist across sessions. Enable any combination; they work independently and feed into the same knowledge base.
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
All three integrations are enabled via `/settings` in the TUI.
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
#### OpenCode
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
opencode:
|
|
121
|
+
integration: true
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
OpenCode saves sessions as JSON or SQLite (depending on version). Ei reads them, extracts context per-agent (each agent like Sisyphus gets its own persona), and keeps everything current as sessions accumulate.
|
|
125
|
+
|
|
126
|
+
OpenCode can also *read* Ei's knowledge back out via the [CLI tool](src/cli/README.md) — making it a dynamic, perpetual RAG. That's why it always has context from your other projects.
|
|
127
|
+
|
|
128
|
+
#### Claude Code
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
claudeCode:
|
|
132
|
+
integration: true
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Reads from `~/.claude/projects/` (JSONL session files). All sessions map to a single "Claude Code" persona. Tool calls, thinking blocks, and internal plumbing are stripped — only the conversational content is imported.
|
|
136
|
+
|
|
137
|
+
#### Cursor
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
cursor:
|
|
141
|
+
integration: true
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Reads from Cursor's SQLite databases:
|
|
145
|
+
- **macOS**: `~/Library/Application Support/Cursor/User/`
|
|
146
|
+
- **Windows**: `%APPDATA%\Cursor\User\`
|
|
147
|
+
- **Linux**: `~/.config/Cursor/User/`
|
|
148
|
+
|
|
149
|
+
All sessions map to a single "Cursor" persona.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
Sessions are processed oldest-first, one per queue cycle, so Ei won't overwhelm your LLM provider on first run. See [TUI Readme](tui/README.md)
|
|
118
154
|
|
|
119
155
|
## Built-in Tool Integrations
|
|
120
156
|
|
package/package.json
CHANGED
|
@@ -150,16 +150,44 @@ export function handlePersonaTraitExtraction(response: LLMResponse, state: State
|
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
if (result.length === 0) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const persona = state.persona_getById(personaId);
|
|
158
|
+
if (!persona) {
|
|
159
|
+
console.error(`[handlePersonaTraitExtraction] Persona ${personaId} not found`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
153
163
|
const now = new Date().toISOString();
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
const updatedIds = new Set<string>();
|
|
165
|
+
const patchedTraits: PersonaTrait[] = result.map(delta => {
|
|
166
|
+
if (delta.id === "new") {
|
|
167
|
+
return {
|
|
168
|
+
id: crypto.randomUUID(),
|
|
169
|
+
name: delta.name,
|
|
170
|
+
description: delta.description,
|
|
171
|
+
sentiment: delta.sentiment,
|
|
172
|
+
strength: delta.strength,
|
|
173
|
+
last_updated: now,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
updatedIds.add(delta.id);
|
|
177
|
+
return {
|
|
178
|
+
id: delta.id,
|
|
179
|
+
name: delta.name,
|
|
180
|
+
description: delta.description,
|
|
181
|
+
sentiment: delta.sentiment,
|
|
182
|
+
strength: delta.strength,
|
|
183
|
+
last_updated: now,
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const preservedTraits = persona.traits.filter(t => !updatedIds.has(t.id));
|
|
188
|
+
|
|
189
|
+
const traits: PersonaTrait[] = [...preservedTraits, ...patchedTraits];
|
|
162
190
|
|
|
163
191
|
state.persona_update(personaId, { traits, last_updated: now });
|
|
164
|
-
console.log(`[handlePersonaTraitExtraction]
|
|
192
|
+
console.log(`[handlePersonaTraitExtraction] Applied ${result.length} delta(s) to ${personaDisplayName}, total traits: ${traits.length}`);
|
|
165
193
|
}
|
|
@@ -16,8 +16,6 @@ import {
|
|
|
16
16
|
import { filterMessagesForContext } from "./context-utils.js";
|
|
17
17
|
import { filterHumanDataByVisibility } from "./prompt-context-builder.js";
|
|
18
18
|
|
|
19
|
-
const DEFAULT_CONTEXT_WINDOW_HOURS = 8;
|
|
20
|
-
|
|
21
19
|
// =============================================================================
|
|
22
20
|
// MODEL HELPERS
|
|
23
21
|
// =============================================================================
|
|
@@ -189,9 +187,9 @@ export async function queueHeartbeatCheck(sm: StateManager, personaId: string, i
|
|
|
189
187
|
const model = getModelForPersona(sm, personaId);
|
|
190
188
|
console.log(`[HeartbeatCheck ${persona.display_name}] Queueing heartbeat check (model: ${model})`);
|
|
191
189
|
const human = sm.getHuman();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
const history = sm.messages_get(personaId);
|
|
191
|
+
const contextWindowHours = persona.context_window_hours ?? human.settings?.default_context_window_hours ?? 8;
|
|
192
|
+
const contextHistory = filterMessagesForContext(history, persona.context_boundary, contextWindowHours);
|
|
195
193
|
|
|
196
194
|
if (personaId === "ei") {
|
|
197
195
|
await queueEiHeartbeat(sm, human, contextHistory, isTUI);
|
|
@@ -24,8 +24,6 @@ import {
|
|
|
24
24
|
import { buildChatMessageContent } from "../prompts/message-utils.js";
|
|
25
25
|
import { filterMessagesForContext } from "./context-utils.js";
|
|
26
26
|
|
|
27
|
-
const DEFAULT_CONTEXT_WINDOW_HOURS = 8;
|
|
28
|
-
|
|
29
27
|
// =============================================================================
|
|
30
28
|
// MESSAGE QUERIES
|
|
31
29
|
// =============================================================================
|
|
@@ -270,15 +268,16 @@ export function checkAndQueueHumanExtraction(
|
|
|
270
268
|
// =============================================================================
|
|
271
269
|
|
|
272
270
|
export function fetchMessagesForLLM(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
): import("./types.js").ChatMessage[] {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
271
|
+
sm: StateManager,
|
|
272
|
+
personaId: string
|
|
273
|
+
): import("./types.js").ChatMessage[] {
|
|
274
|
+
const persona = sm.persona_getById(personaId);
|
|
275
|
+
if (!persona) return [];
|
|
276
|
+
|
|
277
|
+
const human = sm.getHuman();
|
|
278
|
+
const history = sm.messages_get(personaId);
|
|
279
|
+
const contextWindowHours = persona.context_window_hours ?? human.settings?.default_context_window_hours ?? 8;
|
|
280
|
+
const filteredHistory = filterMessagesForContext(history, persona.context_boundary, contextWindowHours);
|
|
282
281
|
|
|
283
282
|
return filteredHistory.reduce<import("./types.js").ChatMessage[]>((acc, m) => {
|
|
284
283
|
const content = buildChatMessageContent(m);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LLMRequestType, LLMPriority, LLMNextStep,
|
|
1
|
+
import { LLMRequestType, LLMPriority, LLMNextStep, type CeremonyConfig, type PersonaTopic, type Topic, type Message, type DataItemBase } from "../types.js";
|
|
2
2
|
import type { StateManager } from "../state-manager.js";
|
|
3
3
|
import { applyDecayToValue } from "../utils/index.js";
|
|
4
4
|
import {
|
|
@@ -309,14 +309,17 @@ export function prunePersonaMessages(personaId: string, state: StateManager): vo
|
|
|
309
309
|
// Sort first — injected messages (session update, archive scan) may be out of order.
|
|
310
310
|
state.messages_sort(personaId);
|
|
311
311
|
const messages = state.messages_get(personaId);
|
|
312
|
-
|
|
312
|
+
const human = state.getHuman();
|
|
313
|
+
const minCount = human.settings?.message_min_count ?? 200;
|
|
314
|
+
const maxAgeDays = human.settings?.message_max_age_days ?? 14;
|
|
315
|
+
if (messages.length <= minCount) return;
|
|
313
316
|
|
|
314
|
-
const cutoffMs = Date.now() - (
|
|
317
|
+
const cutoffMs = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
|
|
315
318
|
|
|
316
319
|
// Messages are sorted by timestamp (oldest first from messages_sort)
|
|
317
320
|
const toRemove: string[] = [];
|
|
318
321
|
for (const m of messages) {
|
|
319
|
-
if (messages.length - toRemove.length <=
|
|
322
|
+
if (messages.length - toRemove.length <= minCount) break;
|
|
320
323
|
|
|
321
324
|
const msgMs = new Date(m.timestamp).getTime();
|
|
322
325
|
if (msgMs >= cutoffMs) break; // Sorted by time, no more old ones
|
package/src/core/processor.ts
CHANGED
|
@@ -105,7 +105,6 @@ import {
|
|
|
105
105
|
} from "./queue-manager.js";
|
|
106
106
|
|
|
107
107
|
const DEFAULT_LOOP_INTERVAL_MS = 100;
|
|
108
|
-
const DEFAULT_CONTEXT_WINDOW_HOURS = 8;
|
|
109
108
|
const DEFAULT_OPENCODE_POLLING_MS = 1800000;
|
|
110
109
|
const DEFAULT_CLAUDE_CODE_POLLING_MS = 1800000;
|
|
111
110
|
const DEFAULT_CURSOR_POLLING_MS = 1800000;
|
|
@@ -679,6 +678,26 @@ export class Processor {
|
|
|
679
678
|
modified = true;
|
|
680
679
|
}
|
|
681
680
|
|
|
681
|
+
if (human.settings.default_heartbeat_ms == null) {
|
|
682
|
+
human.settings.default_heartbeat_ms = 1800000;
|
|
683
|
+
modified = true;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (human.settings.default_context_window_hours == null) {
|
|
687
|
+
human.settings.default_context_window_hours = 8;
|
|
688
|
+
modified = true;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (human.settings.message_min_count == null) {
|
|
692
|
+
human.settings.message_min_count = 200;
|
|
693
|
+
modified = true;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (human.settings.message_max_age_days == null) {
|
|
697
|
+
human.settings.message_max_age_days = 14;
|
|
698
|
+
modified = true;
|
|
699
|
+
}
|
|
700
|
+
|
|
682
701
|
if (modified) {
|
|
683
702
|
this.stateManager.setHuman(human);
|
|
684
703
|
console.log(`[Processor] Seeded missing settings`);
|
|
@@ -881,7 +900,6 @@ const toolNextSteps = new Set([
|
|
|
881
900
|
|
|
882
901
|
private async checkScheduledTasks(): Promise<void> {
|
|
883
902
|
const now = Date.now();
|
|
884
|
-
const DEFAULT_HEARTBEAT_DELAY_MS = 1800000;
|
|
885
903
|
|
|
886
904
|
const human = this.stateManager.getHuman();
|
|
887
905
|
|
|
@@ -926,7 +944,8 @@ const toolNextSteps = new Set([
|
|
|
926
944
|
for (const persona of this.stateManager.persona_getAll()) {
|
|
927
945
|
if (persona.is_paused || persona.is_archived) continue;
|
|
928
946
|
|
|
929
|
-
const
|
|
947
|
+
const defaultHeartbeatMs = this.stateManager.getHuman().settings?.default_heartbeat_ms ?? 1800000;
|
|
948
|
+
const heartbeatDelay = persona.heartbeat_delay_ms ?? defaultHeartbeatMs;
|
|
930
949
|
const lastActivity = persona.last_activity
|
|
931
950
|
? new Date(persona.last_activity).getTime()
|
|
932
951
|
: 0;
|
|
@@ -939,9 +958,11 @@ const toolNextSteps = new Set([
|
|
|
939
958
|
const timeSinceHeartbeat = now - lastHeartbeat;
|
|
940
959
|
|
|
941
960
|
if (timeSinceHeartbeat >= heartbeatDelay) {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
961
|
+
const history = this.stateManager.messages_get(persona.id);
|
|
962
|
+
const contextWindowHours =
|
|
963
|
+
persona.context_window_hours
|
|
964
|
+
?? this.stateManager.getHuman().settings?.default_context_window_hours
|
|
965
|
+
?? 8;
|
|
945
966
|
const contextHistory = filterMessagesForContext(
|
|
946
967
|
history,
|
|
947
968
|
persona.context_boundary,
|
|
@@ -80,6 +80,10 @@ export interface HumanSettings {
|
|
|
80
80
|
skip_quote_delete_confirm?: boolean;
|
|
81
81
|
name_display?: string;
|
|
82
82
|
time_mode?: "24h" | "12h" | "local" | "utc";
|
|
83
|
+
default_heartbeat_ms?: number;
|
|
84
|
+
default_context_window_hours?: number;
|
|
85
|
+
message_min_count?: number;
|
|
86
|
+
message_max_age_days?: number;
|
|
83
87
|
accounts?: ProviderAccount[];
|
|
84
88
|
sync?: SyncCredentials;
|
|
85
89
|
opencode?: OpenCodeSettings;
|
|
@@ -147,10 +151,6 @@ export interface PersonaCreationInput {
|
|
|
147
151
|
// Steps - "57:3"."inputs"."steps"
|
|
148
152
|
// Cfg - "57:3"."inputs"."cfg"
|
|
149
153
|
export const COMFY_PROMPT_TEMPLATE = {"9":{"inputs":{"filename_prefix":"z-image-turbo","images":["57:8",0]},"class_type":"SaveImage","_meta":{"title":"Save Image"}},"57:30":{"inputs":{"clip_name":"qwen_3_4b.safetensors","type":"lumina2","device":"default"},"class_type":"CLIPLoader","_meta":{"title":"Load CLIP"}},"57:29":{"inputs":{"vae_name":"ae.safetensors"},"class_type":"VAELoader","_meta":{"title":"Load VAE"}},"57:33":{"inputs":{"conditioning":["57:27",0]},"class_type":"ConditioningZeroOut","_meta":{"title":"ConditioningZeroOut"}},"57:8":{"inputs":{"samples":["57:3",0],"vae":["57:29",0]},"class_type":"VAEDecode","_meta":{"title":"VAE Decode"}},"57:28":{"inputs":{"unet_name":"z_image_turbo_bf16.safetensors","weight_dtype":"default"},"class_type":"UNETLoader","_meta":{"title":"Load Diffusion Model"}},"57:27":{"inputs":{"text":"This is a test prompt","clip":["57:30",0]},"class_type":"CLIPTextEncode","_meta":{"title":"CLIP Text Encode (Prompt)"}},"57:13":{"inputs":{"width":768,"height":768,"batch_size":1},"class_type":"EmptySD3LatentImage","_meta":{"title":"EmptySD3LatentImage"}},"57:11":{"inputs":{"shift":3,"model":["57:28",0]},"class_type":"ModelSamplingAuraFlow","_meta":{"title":"ModelSamplingAuraFlow"}},"57:3":{"inputs":{"seed":407776369182481,"steps":8,"cfg":1,"sampler_name":"res_multistep","scheduler":"simple","denoise":1,"model":["57:11",0],"positive":["57:27",0],"negative":["57:33",0],"latent_image":["57:13",0]},"class_type":"KSampler","_meta":{"title":"KSampler"}}};
|
|
150
|
-
// Message pruning thresholds (shared by ceremony and import)
|
|
151
|
-
export const MESSAGE_MIN_COUNT = 200;
|
|
152
|
-
export const MESSAGE_MAX_AGE_DAYS = 14;
|
|
153
|
-
|
|
154
154
|
// DLQ rolloff thresholds
|
|
155
155
|
export const DLQ_MAX_COUNT = 50;
|
|
156
156
|
export const DLQ_MAX_AGE_DAYS = 14;
|
|
@@ -6,6 +6,7 @@ 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 => ({
|
|
9
|
+
id: t.id,
|
|
9
10
|
name: t.name,
|
|
10
11
|
description: t.description,
|
|
11
12
|
sentiment: t.sentiment,
|
|
@@ -44,7 +45,8 @@ You are analyzing a conversation to detect EXPLICIT requests for ${personaName}
|
|
|
44
45
|
- Add traits the user didn't explicitly request
|
|
45
46
|
- Infer traits from general conversation
|
|
46
47
|
- Remove traits without explicit feedback
|
|
47
|
-
- Confuse topics/interests with communication traits
|
|
48
|
+
- Confuse topics/interests with communication traits
|
|
49
|
+
- Return traits that don't need to change`;
|
|
48
50
|
|
|
49
51
|
const fieldsFragment = `# Fields
|
|
50
52
|
|
|
@@ -70,16 +72,28 @@ ${formatTraitsForPrompt(data.current_traits)}
|
|
|
70
72
|
|
|
71
73
|
1. ONLY analyze "Most Recent Messages" - earlier messages are context only
|
|
72
74
|
2. ONLY detect EXPLICIT behavior change requests
|
|
73
|
-
3. Return
|
|
75
|
+
3. Return ONLY traits that need to change or be added — omit unchanged traits
|
|
76
|
+
4. If nothing changed, return an empty array \`[]\`
|
|
77
|
+
|
|
78
|
+
**To update an existing trait:** use its \`id\` from the Current TRAITS list above.
|
|
79
|
+
**To add a new trait:** use \`"id": "new"\`.
|
|
74
80
|
|
|
75
81
|
**Return JSON:**
|
|
76
82
|
\`\`\`json
|
|
77
83
|
[
|
|
78
84
|
{
|
|
79
|
-
"
|
|
80
|
-
"
|
|
85
|
+
"id": "existing-guid-from-current-traits",
|
|
86
|
+
"name": "Existing Trait Name",
|
|
87
|
+
"description": "Updated instruction",
|
|
81
88
|
"sentiment": 0.3,
|
|
82
89
|
"strength": 0.7
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "new",
|
|
93
|
+
"name": "Brand New Trait",
|
|
94
|
+
"description": "A brief instruction on how the trait is exhibited",
|
|
95
|
+
"sentiment": 0.0,
|
|
96
|
+
"strength": 0.5
|
|
83
97
|
}
|
|
84
98
|
]
|
|
85
99
|
\`\`\``;
|
|
@@ -111,7 +125,7 @@ ${earlierSection}${recentSection}
|
|
|
111
125
|
|
|
112
126
|
Analyze the "Most Recent Messages" for EXPLICIT requests to change ${personaName}'s communication style.
|
|
113
127
|
|
|
114
|
-
Return the
|
|
128
|
+
Return ONLY the traits that need to change or be added. Return \`[]\` if nothing changed.`;
|
|
115
129
|
|
|
116
130
|
return { system, user };
|
|
117
131
|
}
|
package/tui/README.md
CHANGED
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Ei TUI is built with OpenTUI and SolidJS.
|
|
4
4
|
|
|
5
|
-
OpenCode
|
|
5
|
+
Coding tool integrations (OpenCode, Claude Code, Cursor): enable via `/settings` · export data via [CLI](../src/cli/README.md)
|
|
6
|
+
|
|
7
|
+
## Coding Tool Integrations
|
|
8
|
+
|
|
9
|
+
Enable any or all three in `/settings`. They work independently and feed into the same knowledge base.
|
|
10
|
+
|
|
11
|
+
| Tool | Settings key | Session data location |
|
|
12
|
+
|------|-------------|----------------------|
|
|
13
|
+
| OpenCode | `opencode.integration: true` | OpenCode's local SQLite / JSON session store |
|
|
14
|
+
| Claude Code | `claudeCode.integration: true` | `~/.claude/projects/` (JSONL files) |
|
|
15
|
+
| Cursor | `cursor.integration: true` | `~/Library/Application Support/Cursor/User/` (macOS)<br>`%APPDATA%\Cursor\User\` (Windows)<br>`~/.config/Cursor/User/` (Linux) |
|
|
16
|
+
|
|
17
|
+
Sessions are processed oldest-first, one per queue cycle. On first run Ei works through your backlog gradually — it won't flood your LLM provider.
|
|
18
|
+
|
|
19
|
+
OpenCode also supports reading Ei's extracted knowledge back out via the [CLI tool](../src/cli/README.md), giving it persistent memory across sessions.
|
|
6
20
|
|
|
7
21
|
# Installation
|
|
8
22
|
|
|
@@ -76,6 +76,7 @@ const PLACEHOLDER_LONG_DESC = "Detailed description of this persona's personalit
|
|
|
76
76
|
interface YAMLTrait {
|
|
77
77
|
name: string;
|
|
78
78
|
description: string;
|
|
79
|
+
sentiment: number;
|
|
79
80
|
strength: number;
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -91,6 +92,7 @@ interface YAMLPersonaTopic {
|
|
|
91
92
|
const PLACEHOLDER_TRAIT: YAMLTrait = {
|
|
92
93
|
name: "Example Trait",
|
|
93
94
|
description: "Delete this placeholder or modify it to define a real trait",
|
|
95
|
+
sentiment: 0,
|
|
94
96
|
strength: 0.5,
|
|
95
97
|
};
|
|
96
98
|
const PLACEHOLDER_TOPIC: YAMLPersonaTopic = {
|
|
@@ -196,8 +198,8 @@ export function newPersonaFromYAML(yamlContent: string, allTools?: ToolDefinitio
|
|
|
196
198
|
id: crypto.randomUUID(),
|
|
197
199
|
name: t.name,
|
|
198
200
|
description: t.description,
|
|
201
|
+
sentiment: t.sentiment ?? 0,
|
|
199
202
|
strength: t.strength,
|
|
200
|
-
sentiment: 0,
|
|
201
203
|
last_updated: new Date().toISOString(),
|
|
202
204
|
});
|
|
203
205
|
}
|
|
@@ -275,7 +277,7 @@ export function personaToYAML(persona: PersonaEntity, allGroups?: string[], allT
|
|
|
275
277
|
groups_visible: groupsForYAML,
|
|
276
278
|
traits: useTraitPlaceholder
|
|
277
279
|
? [PLACEHOLDER_TRAIT]
|
|
278
|
-
: persona.traits.map(({ name, description, strength }) => ({ name, description, strength: strength ?? 0.5 })),
|
|
280
|
+
: persona.traits.map(({ name, description, sentiment, strength }) => ({ name, description, sentiment: sentiment ?? 0, strength: strength ?? 0.5 })),
|
|
279
281
|
topics: useTopicPlaceholder
|
|
280
282
|
? [PLACEHOLDER_TOPIC]
|
|
281
283
|
: persona.topics.map(({ name, perspective, approach, personal_stake, exposure_current, exposure_desired }) => ({
|
|
@@ -320,8 +322,8 @@ export function personaFromYAML(yamlContent: string, original: PersonaEntity, al
|
|
|
320
322
|
id: existing?.id ?? crypto.randomUUID(),
|
|
321
323
|
name: t.name,
|
|
322
324
|
description: t.description,
|
|
325
|
+
sentiment: t.sentiment ?? existing?.sentiment ?? 0,
|
|
323
326
|
strength: t.strength,
|
|
324
|
-
sentiment: existing?.sentiment ?? 0,
|
|
325
327
|
last_updated: new Date().toISOString(),
|
|
326
328
|
});
|
|
327
329
|
}
|
|
@@ -502,11 +504,16 @@ interface EditableSettingsData {
|
|
|
502
504
|
rewrite_model?: string | null;
|
|
503
505
|
time_mode?: "24h" | "12h" | "local" | "utc" | null;
|
|
504
506
|
name_display?: string | null;
|
|
507
|
+
default_heartbeat_ms?: number | null;
|
|
508
|
+
default_context_window_hours?: number | null;
|
|
509
|
+
message_min_count?: number | null;
|
|
510
|
+
message_max_age_days?: number | null;
|
|
505
511
|
ceremony?: {
|
|
506
512
|
time: string;
|
|
507
513
|
decay_rate?: number | null;
|
|
508
514
|
explore_threshold?: number | null;
|
|
509
515
|
dedup_threshold?: number | null;
|
|
516
|
+
event_window_hours?: number | null;
|
|
510
517
|
};
|
|
511
518
|
opencode?: {
|
|
512
519
|
integration?: boolean | null;
|
|
@@ -545,11 +552,16 @@ export function settingsToYAML(settings: HumanSettings | undefined): string {
|
|
|
545
552
|
rewrite_model: settings?.rewrite_model ?? null,
|
|
546
553
|
time_mode: settings?.time_mode ?? null,
|
|
547
554
|
name_display: settings?.name_display ?? null,
|
|
555
|
+
default_heartbeat_ms: settings?.default_heartbeat_ms ?? 1800000,
|
|
556
|
+
default_context_window_hours: settings?.default_context_window_hours ?? 8,
|
|
557
|
+
message_min_count: settings?.message_min_count ?? 200,
|
|
558
|
+
message_max_age_days: settings?.message_max_age_days ?? 14,
|
|
548
559
|
ceremony: {
|
|
549
560
|
time: settings?.ceremony?.time ?? "09:00",
|
|
550
561
|
decay_rate: settings?.ceremony?.decay_rate ?? null,
|
|
551
562
|
explore_threshold: settings?.ceremony?.explore_threshold ?? null,
|
|
552
563
|
dedup_threshold: settings?.ceremony?.dedup_threshold ?? null,
|
|
564
|
+
event_window_hours: settings?.ceremony?.event_window_hours ?? null,
|
|
553
565
|
},
|
|
554
566
|
opencode: {
|
|
555
567
|
integration: settings?.opencode?.integration ?? false,
|
|
@@ -601,6 +613,7 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
|
|
|
601
613
|
decay_rate: nullToUndefined(data.ceremony.decay_rate),
|
|
602
614
|
explore_threshold: nullToUndefined(data.ceremony.explore_threshold),
|
|
603
615
|
dedup_threshold: nullToUndefined(data.ceremony.dedup_threshold),
|
|
616
|
+
event_window_hours: nullToUndefined(data.ceremony.event_window_hours),
|
|
604
617
|
last_ceremony: original?.ceremony?.last_ceremony,
|
|
605
618
|
};
|
|
606
619
|
}
|
|
@@ -667,6 +680,10 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
|
|
|
667
680
|
rewrite_model: nullToUndefined(data.rewrite_model),
|
|
668
681
|
time_mode: nullToUndefined(data.time_mode),
|
|
669
682
|
name_display: nullToUndefined(data.name_display),
|
|
683
|
+
default_heartbeat_ms: nullToUndefined(data.default_heartbeat_ms),
|
|
684
|
+
default_context_window_hours: nullToUndefined(data.default_context_window_hours),
|
|
685
|
+
message_min_count: nullToUndefined(data.message_min_count),
|
|
686
|
+
message_max_age_days: nullToUndefined(data.message_max_age_days),
|
|
670
687
|
ceremony,
|
|
671
688
|
opencode,
|
|
672
689
|
claudeCode,
|