ei-tui 1.6.3 → 1.6.5
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/package.json +1 -1
- package/src/cli/README.md +13 -0
- package/src/cli/install.ts +708 -0
- package/src/cli/session-context.ts +98 -0
- package/src/cli.ts +82 -808
- package/src/core/bootstrap-tools.ts +486 -0
- package/src/core/handlers/document-segmentation.ts +1 -2
- package/src/core/handlers/heartbeat.ts +3 -2
- package/src/core/handlers/persona-response.ts +5 -4
- package/src/core/handlers/rooms.ts +6 -5
- package/src/core/integration-sync-manager.ts +482 -0
- package/src/core/message-manager.ts +2 -1
- package/src/core/migrations.ts +297 -0
- package/src/core/orchestrators/ceremony.ts +2 -1
- package/src/core/processor.ts +17 -1220
- package/src/core/room-manager.ts +17 -4
- package/src/core/state-manager.ts +2 -1
- package/src/integrations/slack/importer.ts +1 -1
- package/tui/src/components/PromptInput.tsx +5 -1
- package/tui/src/storage/file.ts +6 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { StateManager } from "./state-manager.js";
|
|
2
|
+
import type { Fact } from "./types.js";
|
|
3
|
+
import { BUILT_IN_FACTS } from "./constants/built-in-facts.js";
|
|
4
|
+
import { isQualifiedMessageId, qualifyEiMessage, qualifyOpenCodeMessage } from "./utils/message-id.js";
|
|
5
|
+
import type { IOpenCodeReader } from "../integrations/opencode/types.js";
|
|
6
|
+
|
|
7
|
+
export function seedBuiltinFacts(stateManager: StateManager): void {
|
|
8
|
+
const human = stateManager.getHuman();
|
|
9
|
+
const existingFactNames = new Set(human.facts.map(f => f.name));
|
|
10
|
+
|
|
11
|
+
const now = new Date().toISOString();
|
|
12
|
+
let seededCount = 0;
|
|
13
|
+
|
|
14
|
+
for (const builtInFact of BUILT_IN_FACTS) {
|
|
15
|
+
if (existingFactNames.has(builtInFact.name)) continue;
|
|
16
|
+
|
|
17
|
+
const newFact: Fact = {
|
|
18
|
+
id: crypto.randomUUID(),
|
|
19
|
+
name: builtInFact.name,
|
|
20
|
+
description: '',
|
|
21
|
+
sentiment: 0,
|
|
22
|
+
validated_date: '',
|
|
23
|
+
last_updated: now,
|
|
24
|
+
learned_on: now,
|
|
25
|
+
};
|
|
26
|
+
human.facts.push(newFact);
|
|
27
|
+
seededCount++;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (seededCount > 0) {
|
|
31
|
+
stateManager.setHuman(human);
|
|
32
|
+
console.log(`[Processor] Seeded ${seededCount} built-in facts`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function migrateLearnedOn(stateManager: StateManager): void {
|
|
37
|
+
const human = stateManager.getHuman();
|
|
38
|
+
|
|
39
|
+
const backfill = <T extends { learned_on?: string; last_updated: string }>(items: T[]): T[] =>
|
|
40
|
+
items.map(item => item.learned_on ? item : { ...item, learned_on: item.last_updated });
|
|
41
|
+
|
|
42
|
+
const facts = backfill(human.facts);
|
|
43
|
+
const topics = backfill(human.topics);
|
|
44
|
+
const people = backfill(human.people);
|
|
45
|
+
|
|
46
|
+
const changed =
|
|
47
|
+
facts.some((f, i) => f !== human.facts[i]) ||
|
|
48
|
+
topics.some((t, i) => t !== human.topics[i]) ||
|
|
49
|
+
people.some((p, i) => p !== human.people[i]);
|
|
50
|
+
|
|
51
|
+
if (changed) {
|
|
52
|
+
stateManager.setHuman({ ...human, facts, topics, people });
|
|
53
|
+
console.log("[Processor] Backfilled learned_on for existing data items");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function migrateMessageIds(stateManager: StateManager, isTUI: boolean): Promise<void> {
|
|
58
|
+
try {
|
|
59
|
+
let msgRewrites = 0;
|
|
60
|
+
let quoteRewrites = 0;
|
|
61
|
+
|
|
62
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
63
|
+
|
|
64
|
+
const personas = stateManager.persona_getAll();
|
|
65
|
+
for (const persona of personas) {
|
|
66
|
+
for (const msg of stateManager.messages_get(persona.id)) {
|
|
67
|
+
if (!msg.external && UUID_PATTERN.test(msg.id)) {
|
|
68
|
+
stateManager.messages_update(persona.id, msg.id, { id: qualifyEiMessage(msg.id) });
|
|
69
|
+
msgRewrites++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const rooms = stateManager.getRoomList(true);
|
|
75
|
+
for (const room of rooms) {
|
|
76
|
+
const roomIdRewrites = new Map<string, string>();
|
|
77
|
+
|
|
78
|
+
for (const msg of stateManager.getRoomMessages(room.id).slice()) {
|
|
79
|
+
if (UUID_PATTERN.test(msg.id)) {
|
|
80
|
+
const fqId = qualifyEiMessage(msg.id);
|
|
81
|
+
roomIdRewrites.set(msg.id, fqId);
|
|
82
|
+
stateManager.updateRoomMessage(room.id, msg.id, { id: fqId });
|
|
83
|
+
msgRewrites++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let parentRewrites = 0;
|
|
88
|
+
for (const msg of stateManager.getRoomMessages(room.id)) {
|
|
89
|
+
const pid = msg.parent_id;
|
|
90
|
+
if (pid && UUID_PATTERN.test(pid)) {
|
|
91
|
+
const fqPid = roomIdRewrites.get(pid) ?? qualifyEiMessage(pid);
|
|
92
|
+
stateManager.updateRoomMessage(room.id, msg.id, { parent_id: fqPid });
|
|
93
|
+
parentRewrites++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const activeNode = room.active_node_id;
|
|
98
|
+
if (activeNode && UUID_PATTERN.test(activeNode)) {
|
|
99
|
+
const fqActive = roomIdRewrites.get(activeNode) ?? qualifyEiMessage(activeNode);
|
|
100
|
+
stateManager.updateRoom(room.id, { active_node_id: fqActive });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (parentRewrites > 0) {
|
|
104
|
+
msgRewrites += parentRewrites;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const human = stateManager.getHuman();
|
|
109
|
+
const quotes = human.quotes ?? [];
|
|
110
|
+
|
|
111
|
+
const eiUuidMap = new Map<string, string>();
|
|
112
|
+
for (const persona of personas) {
|
|
113
|
+
for (const msg of stateManager.messages_get(persona.id)) {
|
|
114
|
+
if (msg.id.startsWith("ei:")) eiUuidMap.set(msg.id.slice(3), msg.id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const room of rooms) {
|
|
118
|
+
for (const msg of stateManager.getRoomMessages(room.id)) {
|
|
119
|
+
if (msg.id.startsWith("ei:")) eiUuidMap.set(msg.id.slice(3), msg.id);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const MSG_PATTERN = /^msg_[a-zA-Z0-9]+$/;
|
|
124
|
+
|
|
125
|
+
let openCodeReader: IOpenCodeReader | null = null;
|
|
126
|
+
if (isTUI) {
|
|
127
|
+
const { createOpenCodeReader } = await import("../integrations/opencode/reader-factory.js");
|
|
128
|
+
openCodeReader = await createOpenCodeReader().catch(() => null);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const updatedQuotes: typeof quotes = [];
|
|
132
|
+
for (const quote of quotes) {
|
|
133
|
+
const mid = quote.message_id;
|
|
134
|
+
if (!mid || isQualifiedMessageId(mid)) {
|
|
135
|
+
updatedQuotes.push(quote);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (MSG_PATTERN.test(mid)) {
|
|
140
|
+
if (openCodeReader) {
|
|
141
|
+
const ocWindow = await openCodeReader.getMessageById(mid).catch(() => null);
|
|
142
|
+
if (ocWindow) {
|
|
143
|
+
const { getMachineId } = await import("../integrations/machine-id.js");
|
|
144
|
+
updatedQuotes.push({ ...quote, message_id: qualifyOpenCodeMessage(getMachineId(), ocWindow.session.id, mid) });
|
|
145
|
+
quoteRewrites++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
updatedQuotes.push(quote);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (UUID_PATTERN.test(mid)) {
|
|
154
|
+
const fqId = eiUuidMap.get(mid);
|
|
155
|
+
if (fqId) {
|
|
156
|
+
updatedQuotes.push({ ...quote, message_id: fqId });
|
|
157
|
+
quoteRewrites++;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
updatedQuotes.push(quote);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
updatedQuotes.push(quote);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (quoteRewrites > 0) {
|
|
168
|
+
stateManager.setHuman({ ...human, quotes: updatedQuotes });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (msgRewrites > 0 || quoteRewrites > 0) {
|
|
172
|
+
console.log(`[Processor] migrateMessageIds: rewrote ${msgRewrites} message IDs, ${quoteRewrites} quote message_ids`);
|
|
173
|
+
}
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.error("[Processor] migrateMessageIds failed, continuing:", err);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function migrateSlackToMultiWorkspace(stateManager: StateManager): void {
|
|
180
|
+
const human = stateManager.getHuman();
|
|
181
|
+
const slack = human.settings?.slack as Record<string, unknown> | undefined;
|
|
182
|
+
if (!slack) return;
|
|
183
|
+
|
|
184
|
+
const hasLegacyAuth = "auth" in slack && slack.auth != null;
|
|
185
|
+
const hasLegacyIntegration = "integration" in slack;
|
|
186
|
+
if (!hasLegacyAuth && !hasLegacyIntegration) return;
|
|
187
|
+
|
|
188
|
+
const legacyAuth = slack.auth as Record<string, unknown> | undefined;
|
|
189
|
+
const workspaceId = (legacyAuth?.workspace_id as string | undefined) ?? "unknown";
|
|
190
|
+
|
|
191
|
+
const migratedWorkspace: Record<string, unknown> = {
|
|
192
|
+
integration: slack.integration,
|
|
193
|
+
extraction_model: slack.extraction_model,
|
|
194
|
+
last_sync: slack.last_sync,
|
|
195
|
+
backfill_days: slack.backfill_days,
|
|
196
|
+
broadcast_threshold: slack.broadcast_threshold,
|
|
197
|
+
channel_overrides: slack.channel_overrides,
|
|
198
|
+
channels: slack.channels,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (legacyAuth) {
|
|
202
|
+
migratedWorkspace.auth = {
|
|
203
|
+
type: "oauth",
|
|
204
|
+
token: legacyAuth.token,
|
|
205
|
+
refresh_token: legacyAuth.refresh_token,
|
|
206
|
+
workspace_name: legacyAuth.workspace_name,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
stateManager.setHuman({
|
|
211
|
+
...human,
|
|
212
|
+
settings: {
|
|
213
|
+
...human.settings,
|
|
214
|
+
slack: {
|
|
215
|
+
polling_interval_ms: slack.polling_interval_ms as number | undefined,
|
|
216
|
+
workspaces: { [workspaceId]: migratedWorkspace } as unknown as import("../integrations/slack/types.js").SlackSettings["workspaces"],
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
console.log(`[Processor] migrateSlackToMultiWorkspace: migrated legacy slack settings to workspaces[${workspaceId}]`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function seedSettings(stateManager: StateManager): void {
|
|
225
|
+
const human = stateManager.getHuman();
|
|
226
|
+
let modified = false;
|
|
227
|
+
|
|
228
|
+
if (!human.settings) {
|
|
229
|
+
human.settings = {};
|
|
230
|
+
modified = true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!human.settings.opencode) {
|
|
234
|
+
human.settings.opencode = {
|
|
235
|
+
integration: false,
|
|
236
|
+
polling_interval_ms: 60000,
|
|
237
|
+
};
|
|
238
|
+
modified = true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!human.settings.claudeCode) {
|
|
242
|
+
human.settings.claudeCode = {
|
|
243
|
+
integration: false,
|
|
244
|
+
polling_interval_ms: 60000,
|
|
245
|
+
};
|
|
246
|
+
modified = true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!human.settings.codex) {
|
|
250
|
+
human.settings.codex = {
|
|
251
|
+
integration: false,
|
|
252
|
+
polling_interval_ms: 60000,
|
|
253
|
+
};
|
|
254
|
+
modified = true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!human.settings.ceremony) {
|
|
258
|
+
human.settings.ceremony = {
|
|
259
|
+
time: "09:00",
|
|
260
|
+
};
|
|
261
|
+
modified = true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!human.settings.backup) {
|
|
265
|
+
human.settings.backup = {
|
|
266
|
+
enabled: false,
|
|
267
|
+
max_backups: 24,
|
|
268
|
+
interval_ms: 3600000,
|
|
269
|
+
};
|
|
270
|
+
modified = true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (human.settings.default_heartbeat_ms == null) {
|
|
274
|
+
human.settings.default_heartbeat_ms = 1800000;
|
|
275
|
+
modified = true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (human.settings.default_context_window_ms == null) {
|
|
279
|
+
human.settings.default_context_window_ms = 28800000;
|
|
280
|
+
modified = true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (human.settings.message_min_count == null) {
|
|
284
|
+
human.settings.message_min_count = 0;
|
|
285
|
+
modified = true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (human.settings.message_max_age_days == null) {
|
|
289
|
+
human.settings.message_max_age_days = 0;
|
|
290
|
+
modified = true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (modified) {
|
|
294
|
+
stateManager.setHuman(human);
|
|
295
|
+
console.log(`[Processor] Seeded missing settings`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -17,6 +17,7 @@ import { buildPersonRewriteScanPrompt } from "../../prompts/ceremony/people-rewr
|
|
|
17
17
|
import { buildTopicRewriteScanPrompt } from "../../prompts/ceremony/topic-rewrite.js";
|
|
18
18
|
import { buildReflectionCriticPrompt } from "../../prompts/reflection/index.js";
|
|
19
19
|
import { getModelForPersona } from "../heartbeat-manager.js";
|
|
20
|
+
import { qualifyEiMessage } from "../utils/message-id.js";
|
|
20
21
|
|
|
21
22
|
const PERSON_LOG_REFLECTION_THRESHOLD = 3000;
|
|
22
23
|
|
|
@@ -641,7 +642,7 @@ function queueReflectionPhase(state: StateManager): boolean {
|
|
|
641
642
|
console.log(`[ceremony:reflection] ${persona.display_name} is linked to multiple person records (${names}) — skipping reflection, writing Ei warning`);
|
|
642
643
|
|
|
643
644
|
const warning: Message = {
|
|
644
|
-
id: crypto.randomUUID(),
|
|
645
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
645
646
|
role: "system",
|
|
646
647
|
content: `During today's ceremony, I noticed that **${persona.display_name}** is connected to multiple person records: ${names}. This might be intentional — if you created a composite persona — but if not, you may want to check the identifiers on those records. Reflection for ${persona.display_name} has been paused until this is resolved.`,
|
|
647
648
|
timestamp: new Date().toISOString(),
|