ei-tui 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/package.json +1 -1
- package/src/cli/README.md +17 -12
- package/src/cli/commands/personas.ts +12 -0
- package/src/cli/mcp.ts +2 -2
- package/src/cli/retrieval.ts +86 -8
- package/src/cli.ts +8 -5
- package/src/core/constants/seed-traits.ts +29 -0
- package/src/core/context-utils.ts +1 -0
- package/src/core/handlers/human-matching.ts +86 -56
- package/src/core/handlers/index.ts +5 -0
- package/src/core/handlers/persona-preview.ts +7 -0
- package/src/core/handlers/persona-topics.ts +3 -2
- package/src/core/handlers/rooms.ts +176 -0
- package/src/core/handlers/utils.ts +55 -3
- package/src/core/heartbeat-manager.ts +3 -1
- package/src/core/llm-client.ts +1 -1
- package/src/core/message-manager.ts +10 -8
- package/src/core/orchestrators/human-extraction.ts +15 -2
- package/src/core/orchestrators/index.ts +1 -0
- package/src/core/orchestrators/persona-generation.ts +4 -0
- package/src/core/orchestrators/persona-topics.ts +2 -1
- package/src/core/orchestrators/room-extraction.ts +318 -0
- package/src/core/persona-manager.ts +16 -5
- package/src/core/personas/opencode-agent.ts +12 -2
- package/src/core/processor.ts +520 -4
- package/src/core/prompt-context-builder.ts +89 -5
- package/src/core/queue-processor.ts +68 -8
- package/src/core/room-manager.ts +408 -0
- package/src/core/state/index.ts +1 -0
- package/src/core/state/personas.ts +12 -2
- package/src/core/state/queue.ts +2 -2
- package/src/core/state/rooms.ts +182 -0
- package/src/core/state-manager.ts +124 -2
- package/src/core/tool-manager.ts +1 -1
- package/src/core/tools/index.ts +15 -0
- package/src/core/types/data-items.ts +3 -1
- package/src/core/types/enums.ts +11 -0
- package/src/core/types/integrations.ts +10 -2
- package/src/core/types/llm.ts +3 -0
- package/src/core/types/rooms.ts +59 -0
- package/src/core/types.ts +1 -0
- package/src/core/utils/decay.ts +14 -8
- package/src/core/utils/exposure.ts +14 -0
- package/src/integrations/claude-code/importer.ts +23 -10
- package/src/integrations/cursor/importer.ts +22 -10
- package/src/integrations/opencode/importer.ts +30 -13
- package/src/prompts/ceremony/dedup.ts +2 -2
- package/src/prompts/generation/from-person.ts +85 -0
- package/src/prompts/generation/index.ts +2 -0
- package/src/prompts/generation/persona.ts +14 -10
- package/src/prompts/generation/seeds.ts +4 -29
- package/src/prompts/generation/types.ts +13 -0
- package/src/prompts/heartbeat/check.ts +1 -1
- package/src/prompts/heartbeat/ei.ts +4 -4
- package/src/prompts/heartbeat/types.ts +1 -0
- package/src/prompts/index.ts +15 -0
- package/src/prompts/message-utils.ts +2 -2
- package/src/prompts/persona/topics-match.ts +7 -6
- package/src/prompts/persona/topics-update.ts +8 -11
- package/src/prompts/persona/types.ts +2 -1
- package/src/prompts/response/index.ts +1 -1
- package/src/prompts/response/sections.ts +20 -8
- package/src/prompts/response/types.ts +6 -0
- package/src/prompts/room/index.ts +115 -0
- package/src/prompts/room/sections.ts +150 -0
- package/src/prompts/room/types.ts +93 -0
- package/tui/README.md +20 -0
- package/tui/src/app.tsx +3 -2
- package/tui/src/commands/activate.tsx +98 -0
- package/tui/src/commands/archive.tsx +54 -25
- package/tui/src/commands/capture.tsx +50 -0
- package/tui/src/commands/dedupe.tsx +2 -7
- package/tui/src/commands/delete.tsx +48 -0
- package/tui/src/commands/details.tsx +7 -0
- package/tui/src/commands/persona.tsx +271 -9
- package/tui/src/commands/room.tsx +261 -0
- package/tui/src/commands/silence.tsx +29 -0
- package/tui/src/components/ArchivedItemsOverlay.tsx +144 -0
- package/tui/src/components/ConfirmOverlay.tsx +6 -0
- package/tui/src/components/ConflictOverlay.tsx +6 -0
- package/tui/src/components/HelpOverlay.tsx +6 -1
- package/tui/src/components/LoadingOverlay.tsx +51 -0
- package/tui/src/components/MessageList.tsx +1 -18
- package/tui/src/components/PersonPickerOverlay.tsx +121 -0
- package/tui/src/components/PersonaListOverlay.tsx +6 -1
- package/tui/src/components/PromptInput.tsx +141 -8
- package/tui/src/components/ProviderListOverlay.tsx +5 -1
- package/tui/src/components/QuotesOverlay.tsx +5 -1
- package/tui/src/components/RoomMessageList.tsx +179 -0
- package/tui/src/components/Sidebar.tsx +54 -2
- package/tui/src/components/StatusBar.tsx +99 -8
- package/tui/src/components/ToolkitListOverlay.tsx +5 -1
- package/tui/src/components/WelcomeOverlay.tsx +6 -0
- package/tui/src/context/ei.tsx +252 -1
- package/tui/src/context/keyboard.tsx +48 -12
- package/tui/src/util/cyp-editor.tsx +152 -0
- package/tui/src/util/quote-utils.ts +19 -0
- package/tui/src/util/room-editor.tsx +164 -0
- package/tui/src/util/room-logic.ts +8 -0
- package/tui/src/util/room-parser.ts +70 -0
- package/tui/src/util/yaml-serializers.ts +151 -0
|
@@ -213,10 +213,20 @@ export class PersonaState {
|
|
|
213
213
|
return removed;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
messages_getUnextracted(
|
|
216
|
+
messages_getUnextracted(
|
|
217
|
+
personaId: string,
|
|
218
|
+
flag: "f" | "t" | "p" | "e",
|
|
219
|
+
limit?: number,
|
|
220
|
+
external_filter?: "include" | "exclude" | "only"
|
|
221
|
+
): Message[] {
|
|
217
222
|
const data = this.personas.get(personaId);
|
|
218
223
|
if (!data) return [];
|
|
219
|
-
|
|
224
|
+
let unextracted = data.messages.filter(m => m[flag] !== true);
|
|
225
|
+
if (external_filter === "exclude") {
|
|
226
|
+
unextracted = unextracted.filter(m => m.external !== true);
|
|
227
|
+
} else if (external_filter === "only") {
|
|
228
|
+
unextracted = unextracted.filter(m => m.external === true);
|
|
229
|
+
}
|
|
220
230
|
if (limit && unextracted.length > limit) {
|
|
221
231
|
return unextracted.slice(0, limit).map(m => ({ ...m }));
|
|
222
232
|
}
|
package/src/core/state/queue.ts
CHANGED
|
@@ -55,7 +55,7 @@ export class QueueState {
|
|
|
55
55
|
if (this.paused || this.queue.length === 0) return null;
|
|
56
56
|
const available = this.queue.filter(r => r.state === "pending");
|
|
57
57
|
if (available.length === 0) return null;
|
|
58
|
-
const priorityOrder = { high: 0,
|
|
58
|
+
const priorityOrder = { high: 0, judge: 1, room: 2, normal: 3, low: 4 };
|
|
59
59
|
const sorted = [...available].sort(
|
|
60
60
|
(a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]
|
|
61
61
|
);
|
|
@@ -72,7 +72,7 @@ export class QueueState {
|
|
|
72
72
|
if (this.paused || this.queue.length === 0) return null;
|
|
73
73
|
const available = this.queue.filter(r => r.state === "pending");
|
|
74
74
|
if (available.length === 0) return null;
|
|
75
|
-
const priorityOrder = { high: 0,
|
|
75
|
+
const priorityOrder = { high: 0, judge: 1, room: 2, normal: 3, low: 4 };
|
|
76
76
|
const sorted = [...available].sort(
|
|
77
77
|
(a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]
|
|
78
78
|
);
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EI V1 Room State
|
|
3
|
+
* Source of truth: CONTRACTS.md
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RoomEntity, RoomMessage, RoomSummary } from "../types.js";
|
|
7
|
+
|
|
8
|
+
export class RoomState {
|
|
9
|
+
private rooms: Map<string, RoomEntity> = new Map();
|
|
10
|
+
|
|
11
|
+
load(rooms: Record<string, RoomEntity> | undefined): void {
|
|
12
|
+
if (!rooms) return;
|
|
13
|
+
this.rooms = new Map(Object.entries(rooms));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export(): Record<string, RoomEntity> {
|
|
17
|
+
const result: Record<string, RoomEntity> = {};
|
|
18
|
+
for (const [id, room] of this.rooms) {
|
|
19
|
+
result[id] = room;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getAll(includeArchived = false): RoomEntity[] {
|
|
25
|
+
const all = Array.from(this.rooms.values());
|
|
26
|
+
return includeArchived ? all : all.filter(r => !r.is_archived);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getById(id: string): RoomEntity | null {
|
|
30
|
+
return this.rooms.get(id) ?? null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getByName(nameOrAlias: string): RoomEntity | null {
|
|
34
|
+
const search = nameOrAlias.toLowerCase();
|
|
35
|
+
for (const room of this.rooms.values()) {
|
|
36
|
+
if (room.display_name.toLowerCase() === search) return room;
|
|
37
|
+
}
|
|
38
|
+
const partial: RoomEntity[] = [];
|
|
39
|
+
for (const room of this.rooms.values()) {
|
|
40
|
+
if (room.display_name.toLowerCase().includes(search)) partial.push(room);
|
|
41
|
+
}
|
|
42
|
+
return partial.length === 1 ? partial[0] : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
add(room: RoomEntity): void {
|
|
46
|
+
this.rooms.set(room.id, room);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
update(roomId: string, updates: Partial<RoomEntity>): boolean {
|
|
50
|
+
const room = this.rooms.get(roomId);
|
|
51
|
+
if (!room) return false;
|
|
52
|
+
Object.assign(room, updates, { last_updated: new Date().toISOString() });
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
archive(roomId: string): boolean {
|
|
57
|
+
const room = this.rooms.get(roomId);
|
|
58
|
+
if (!room) return false;
|
|
59
|
+
room.is_archived = true;
|
|
60
|
+
room.last_updated = new Date().toISOString();
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
delete(roomId: string): boolean {
|
|
65
|
+
return this.rooms.delete(roomId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getSummary(roomId: string): RoomSummary | null {
|
|
69
|
+
const room = this.rooms.get(roomId);
|
|
70
|
+
if (!room) return null;
|
|
71
|
+
return {
|
|
72
|
+
id: room.id,
|
|
73
|
+
display_name: room.display_name,
|
|
74
|
+
mode: room.mode,
|
|
75
|
+
persona_ids: room.persona_ids,
|
|
76
|
+
active_node_id: room.active_node_id,
|
|
77
|
+
is_archived: room.is_archived,
|
|
78
|
+
last_activity: room.last_activity,
|
|
79
|
+
unread_count: this.messages_countUnread(roomId),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
messages_get(roomId: string): RoomMessage[] {
|
|
84
|
+
return this.rooms.get(roomId)?.messages ?? [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
messages_getActivePath(roomId: string): RoomMessage[] {
|
|
88
|
+
const room = this.rooms.get(roomId);
|
|
89
|
+
if (!room || !room.active_node_id) return [];
|
|
90
|
+
|
|
91
|
+
const byId = new Map(room.messages.map(m => [m.id, m]));
|
|
92
|
+
const path: RoomMessage[] = [];
|
|
93
|
+
let current: RoomMessage | undefined = byId.get(room.active_node_id);
|
|
94
|
+
while (current) {
|
|
95
|
+
path.unshift(current);
|
|
96
|
+
current = current.parent_id ? byId.get(current.parent_id) : undefined;
|
|
97
|
+
}
|
|
98
|
+
return path;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
messages_getChildren(roomId: string, parentId: string | null): RoomMessage[] {
|
|
102
|
+
const room = this.rooms.get(roomId);
|
|
103
|
+
if (!room) return [];
|
|
104
|
+
return room.messages.filter(m => m.parent_id === parentId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
messages_append(roomId: string, message: RoomMessage): void {
|
|
108
|
+
const room = this.rooms.get(roomId);
|
|
109
|
+
if (!room) return;
|
|
110
|
+
room.messages.push(message);
|
|
111
|
+
room.last_activity = message.timestamp;
|
|
112
|
+
room.last_updated = new Date().toISOString();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
messages_update(roomId: string, messageId: string, updates: Partial<RoomMessage>): boolean {
|
|
116
|
+
const room = this.rooms.get(roomId);
|
|
117
|
+
if (!room) return false;
|
|
118
|
+
const msg = room.messages.find(m => m.id === messageId);
|
|
119
|
+
if (!msg) return false;
|
|
120
|
+
Object.assign(msg, updates);
|
|
121
|
+
room.last_updated = new Date().toISOString();
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
messages_remove(roomId: string, messageIds: string[]): void {
|
|
126
|
+
const room = this.rooms.get(roomId);
|
|
127
|
+
if (!room) return;
|
|
128
|
+
const idsSet = new Set(messageIds);
|
|
129
|
+
room.messages = room.messages.filter(m => !idsSet.has(m.id));
|
|
130
|
+
room.last_updated = new Date().toISOString();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
messages_setActiveNode(roomId: string, messageId: string): boolean {
|
|
134
|
+
const room = this.rooms.get(roomId);
|
|
135
|
+
if (!room) return false;
|
|
136
|
+
const exists = room.messages.some(m => m.id === messageId);
|
|
137
|
+
if (!exists) return false;
|
|
138
|
+
room.active_node_id = messageId;
|
|
139
|
+
room.last_updated = new Date().toISOString();
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
messages_countUnread(roomId: string): number {
|
|
144
|
+
const room = this.rooms.get(roomId);
|
|
145
|
+
if (!room) return 0;
|
|
146
|
+
const activePath = new Set(this.messages_getActivePath(roomId).map(m => m.id));
|
|
147
|
+
return room.messages.filter(m =>
|
|
148
|
+
m.role === "persona" && !m.read && activePath.has(m.id)
|
|
149
|
+
).length;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
messages_markAllRead(roomId: string): number {
|
|
153
|
+
const room = this.rooms.get(roomId);
|
|
154
|
+
if (!room) return 0;
|
|
155
|
+
let count = 0;
|
|
156
|
+
for (const msg of room.messages) {
|
|
157
|
+
if (!msg.read) { msg.read = true; count++; }
|
|
158
|
+
}
|
|
159
|
+
return count;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
messages_getUnextracted(roomId: string, flag: "f" | "t" | "p" | "e"): RoomMessage[] {
|
|
163
|
+
const activePath = new Set(this.messages_getActivePath(roomId).map(m => m.id));
|
|
164
|
+
return (this.rooms.get(roomId)?.messages ?? [])
|
|
165
|
+
.filter(m => activePath.has(m.id) && m[flag] !== true)
|
|
166
|
+
.map(m => ({ ...m }));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
messages_markExtracted(roomId: string, messageIds: string[], flag: "f" | "t" | "p" | "e"): number {
|
|
170
|
+
const room = this.rooms.get(roomId);
|
|
171
|
+
if (!room) return 0;
|
|
172
|
+
const ids = new Set(messageIds);
|
|
173
|
+
let count = 0;
|
|
174
|
+
for (const msg of room.messages) {
|
|
175
|
+
if (ids.has(msg.id) && msg[flag] !== true) {
|
|
176
|
+
msg[flag] = true;
|
|
177
|
+
count++;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return count;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -12,6 +12,10 @@ import type {
|
|
|
12
12
|
QueueFailResult,
|
|
13
13
|
ToolDefinition,
|
|
14
14
|
ToolProvider,
|
|
15
|
+
RoomEntity,
|
|
16
|
+
RoomMessage,
|
|
17
|
+
RoomSummary,
|
|
18
|
+
RoomCreationInput,
|
|
15
19
|
} from "./types.js";
|
|
16
20
|
import { BUILT_IN_FACT_NAMES } from './constants/built-in-facts.js';
|
|
17
21
|
import type { Storage } from "../storage/interface.js";
|
|
@@ -20,12 +24,14 @@ import {
|
|
|
20
24
|
PersonaState,
|
|
21
25
|
QueueState,
|
|
22
26
|
PersistenceState,
|
|
27
|
+
RoomState,
|
|
23
28
|
createDefaultHumanEntity,
|
|
24
29
|
} from "./state/index.js";
|
|
25
30
|
|
|
26
31
|
export class StateManager {
|
|
27
32
|
private humanState = new HumanState();
|
|
28
33
|
private personaState = new PersonaState();
|
|
34
|
+
private roomState = new RoomState();
|
|
29
35
|
private queueState = new QueueState();
|
|
30
36
|
private persistenceState = new PersistenceState();
|
|
31
37
|
private providers: ToolProvider[] = [];
|
|
@@ -40,6 +46,7 @@ export class StateManager {
|
|
|
40
46
|
if (state) {
|
|
41
47
|
this.humanState.load(state.human);
|
|
42
48
|
this.personaState.load(state.personas);
|
|
49
|
+
this.roomState.load(state.rooms);
|
|
43
50
|
this.queueState.load(state.queue);
|
|
44
51
|
this.tools = state.tools ?? [];
|
|
45
52
|
this.providers = state.providers ?? [];
|
|
@@ -230,12 +237,126 @@ export class StateManager {
|
|
|
230
237
|
timestamp: new Date().toISOString(),
|
|
231
238
|
human: this.humanState.get(),
|
|
232
239
|
personas: this.personaState.export(),
|
|
240
|
+
rooms: this.roomState.export(),
|
|
233
241
|
queue: this.queueState.export(),
|
|
234
242
|
providers: this.providers,
|
|
235
243
|
tools: this.tools,
|
|
236
244
|
};
|
|
237
245
|
}
|
|
238
246
|
|
|
247
|
+
getRoomList(includeArchived = false): RoomSummary[] {
|
|
248
|
+
return this.roomState.getAll(includeArchived)
|
|
249
|
+
.map(r => this.roomState.getSummary(r.id)!)
|
|
250
|
+
.sort((a, b) => new Date(b.last_activity).getTime() - new Date(a.last_activity).getTime());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
getRoom(roomId: string): RoomEntity | null {
|
|
254
|
+
return this.roomState.getById(roomId);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
getRoomByName(name: string): RoomEntity | null {
|
|
258
|
+
return this.roomState.getByName(name);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
addRoom(input: RoomCreationInput): RoomEntity {
|
|
262
|
+
const now = new Date().toISOString();
|
|
263
|
+
const initialMessage: RoomMessage = {
|
|
264
|
+
id: crypto.randomUUID(),
|
|
265
|
+
parent_id: null,
|
|
266
|
+
role: "human",
|
|
267
|
+
verbal_response: input.initial_message,
|
|
268
|
+
timestamp: now,
|
|
269
|
+
read: true,
|
|
270
|
+
context_status: "default" as import("./types.js").ContextStatus,
|
|
271
|
+
};
|
|
272
|
+
const room: RoomEntity = {
|
|
273
|
+
id: crypto.randomUUID(),
|
|
274
|
+
display_name: input.display_name,
|
|
275
|
+
entity: "room",
|
|
276
|
+
mode: input.mode,
|
|
277
|
+
persona_ids: input.persona_ids,
|
|
278
|
+
judge_persona_id: input.judge_persona_id,
|
|
279
|
+
active_node_id: initialMessage.id,
|
|
280
|
+
is_archived: false,
|
|
281
|
+
created_at: now,
|
|
282
|
+
last_updated: now,
|
|
283
|
+
last_activity: now,
|
|
284
|
+
messages: [initialMessage],
|
|
285
|
+
};
|
|
286
|
+
this.roomState.add(room);
|
|
287
|
+
this.scheduleSave();
|
|
288
|
+
return room;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
updateRoom(roomId: string, updates: Partial<RoomEntity>): boolean {
|
|
292
|
+
const ok = this.roomState.update(roomId, updates);
|
|
293
|
+
if (ok) this.scheduleSave();
|
|
294
|
+
return ok;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
archiveRoom(roomId: string): boolean {
|
|
298
|
+
const ok = this.roomState.archive(roomId);
|
|
299
|
+
if (ok) this.scheduleSave();
|
|
300
|
+
return ok;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
deleteRoom(roomId: string): boolean {
|
|
304
|
+
const ok = this.roomState.delete(roomId);
|
|
305
|
+
if (ok) this.scheduleSave();
|
|
306
|
+
return ok;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
getRoomMessages(roomId: string): RoomMessage[] {
|
|
310
|
+
return this.roomState.messages_get(roomId);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
getRoomActivePath(roomId: string): RoomMessage[] {
|
|
314
|
+
return this.roomState.messages_getActivePath(roomId);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
getRoomChildren(roomId: string, parentId: string | null): RoomMessage[] {
|
|
318
|
+
return this.roomState.messages_getChildren(roomId, parentId);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
appendRoomMessage(roomId: string, message: RoomMessage): void {
|
|
322
|
+
this.roomState.messages_append(roomId, message);
|
|
323
|
+
this.scheduleSave();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
updateRoomMessage(roomId: string, messageId: string, updates: Partial<RoomMessage>): boolean {
|
|
327
|
+
const ok = this.roomState.messages_update(roomId, messageId, updates);
|
|
328
|
+
if (ok) this.scheduleSave();
|
|
329
|
+
return ok;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
setRoomActiveNode(roomId: string, messageId: string): boolean {
|
|
333
|
+
const ok = this.roomState.messages_setActiveNode(roomId, messageId);
|
|
334
|
+
if (ok) this.scheduleSave();
|
|
335
|
+
return ok;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
removeRoomMessages(roomId: string, messageIds: string[]): void {
|
|
339
|
+
if (messageIds.length === 0) return;
|
|
340
|
+
this.roomState.messages_remove(roomId, messageIds);
|
|
341
|
+
this.scheduleSave();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
markAllRoomMessagesRead(roomId: string): number {
|
|
345
|
+
const count = this.roomState.messages_markAllRead(roomId);
|
|
346
|
+
if (count > 0) this.scheduleSave();
|
|
347
|
+
return count;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
getRoomUnextractedMessages(roomId: string, flag: "f" | "t" | "p" | "e"): RoomMessage[] {
|
|
351
|
+
return this.roomState.messages_getUnextracted(roomId, flag);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
markRoomMessagesExtracted(roomId: string, messageIds: string[], flag: "f" | "t" | "p" | "e"): number {
|
|
355
|
+
const count = this.roomState.messages_markExtracted(roomId, messageIds, flag);
|
|
356
|
+
if (count > 0) this.scheduleSave();
|
|
357
|
+
return count;
|
|
358
|
+
}
|
|
359
|
+
|
|
239
360
|
private scheduleSave(): void {
|
|
240
361
|
this.persistenceState.scheduleSave(this.buildStorageState());
|
|
241
362
|
}
|
|
@@ -421,8 +542,8 @@ export class StateManager {
|
|
|
421
542
|
return result;
|
|
422
543
|
}
|
|
423
544
|
|
|
424
|
-
messages_getUnextracted(personaId: string, flag: "f" | "t" | "p" | "e", limit?: number): Message[] {
|
|
425
|
-
return this.personaState.messages_getUnextracted(personaId, flag, limit);
|
|
545
|
+
messages_getUnextracted(personaId: string, flag: "f" | "t" | "p" | "e", limit?: number, external_filter?: "include" | "exclude" | "only"): Message[] {
|
|
546
|
+
return this.personaState.messages_getUnextracted(personaId, flag, limit, external_filter);
|
|
426
547
|
}
|
|
427
548
|
|
|
428
549
|
messages_markExtracted(personaId: string, messageIds: string[], flag: "f" | "t" | "p" | "e"): number {
|
|
@@ -662,6 +783,7 @@ export class StateManager {
|
|
|
662
783
|
restoreFromState(state: StorageState): void {
|
|
663
784
|
this.humanState.load(state.human);
|
|
664
785
|
this.personaState.load(state.personas);
|
|
786
|
+
this.roomState.load(state.rooms);
|
|
665
787
|
this.queueState.load(state.queue);
|
|
666
788
|
this.providers = state.providers ?? [];
|
|
667
789
|
this.tools = state.tools ?? [];
|
package/src/core/tool-manager.ts
CHANGED
|
@@ -54,7 +54,7 @@ export async function removeToolProvider(sm: StateManager, id: string): Promise<
|
|
|
54
54
|
// =============================================================================
|
|
55
55
|
|
|
56
56
|
export function getToolList(sm: StateManager): ToolDefinition[] {
|
|
57
|
-
return sm.tools_getAll();
|
|
57
|
+
return sm.tools_getAll().filter(t => !t.is_submit);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export function getTool(sm: StateManager, id: string): ToolDefinition | null {
|
package/src/core/tools/index.ts
CHANGED
|
@@ -81,10 +81,25 @@ export function toOpenAITools(tools: ToolDefinition[]): Record<string, unknown>[
|
|
|
81
81
|
name: t.name,
|
|
82
82
|
description: t.description,
|
|
83
83
|
parameters: t.input_schema,
|
|
84
|
+
...(t.is_submit ? { strict: true } : {}),
|
|
84
85
|
},
|
|
85
86
|
}));
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Returns the first tool call in the batch that maps to an is_submit tool, or undefined.
|
|
91
|
+
* When a submit tool is called, its arguments ARE the structured response — no execution needed.
|
|
92
|
+
*/
|
|
93
|
+
export function findSubmitToolCall(
|
|
94
|
+
toolCalls: ToolCall[],
|
|
95
|
+
activeTools: ToolDefinition[]
|
|
96
|
+
): ToolCall | undefined {
|
|
97
|
+
const submitNames = new Set(
|
|
98
|
+
activeTools.filter(t => t.is_submit).map(t => t.name)
|
|
99
|
+
);
|
|
100
|
+
return toolCalls.find(call => submitNames.has(call.name));
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
// =============================================================================
|
|
89
104
|
// Tool call execution
|
|
90
105
|
// =============================================================================
|
|
@@ -67,7 +67,9 @@ export interface Quote {
|
|
|
67
67
|
data_item_ids: string[]; // FK[] to DataItemBase.id
|
|
68
68
|
persona_groups: string[]; // Visibility groups
|
|
69
69
|
text: string; // The quote content
|
|
70
|
-
speaker: "human" | string; //
|
|
70
|
+
speaker: "human" | string; // Actual speaker: "human" or the persona's display_name
|
|
71
|
+
channel?: string; // Display name of the Channel (persona or room) where captured.
|
|
72
|
+
// Undefined on pre-migration quotes.
|
|
71
73
|
timestamp: string; // ISO timestamp (from original message)
|
|
72
74
|
start: number | null; // Character offset in message (null = can't highlight)
|
|
73
75
|
end: number | null; // Character offset in message (null = can't highlight)
|
package/src/core/types/enums.ts
CHANGED
|
@@ -17,6 +17,8 @@ export enum LLMRequestType {
|
|
|
17
17
|
|
|
18
18
|
export enum LLMPriority {
|
|
19
19
|
High = "high",
|
|
20
|
+
Judge = "judge",
|
|
21
|
+
Room = "room",
|
|
20
22
|
Normal = "normal",
|
|
21
23
|
Low = "low",
|
|
22
24
|
}
|
|
@@ -51,6 +53,9 @@ export enum LLMNextStep {
|
|
|
51
53
|
HandleRewriteRewrite = "handleRewriteRewrite",
|
|
52
54
|
HandleDedupCurate = "handleDedupCurate",
|
|
53
55
|
HandleEventScan = "handleEventScan",
|
|
56
|
+
HandleRoomResponse = "handleRoomResponse",
|
|
57
|
+
HandleRoomJudge = "handleRoomJudge",
|
|
58
|
+
HandlePersonaPreview = "handlePersonaPreview",
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
export enum ProviderType {
|
|
@@ -58,3 +63,9 @@ export enum ProviderType {
|
|
|
58
63
|
Storage = "storage",
|
|
59
64
|
Image = "image",
|
|
60
65
|
}
|
|
66
|
+
|
|
67
|
+
export enum RoomMode {
|
|
68
|
+
ChooseYourPath = "choose_your_path",
|
|
69
|
+
FreeForAll = "free_for_all",
|
|
70
|
+
MessagesAgainstPersona = "messages_against_persona",
|
|
71
|
+
}
|
|
@@ -39,6 +39,7 @@ export interface ToolDefinition {
|
|
|
39
39
|
enabled: boolean;
|
|
40
40
|
created_at: string; // ISO timestamp
|
|
41
41
|
max_calls_per_interaction?: number; // Max times LLM may call this tool per response turn. Default: 3.
|
|
42
|
+
is_submit?: boolean; // If true, calling this tool IS the structured response — terminates the tool loop immediately and its arguments become response.parsed
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// =============================================================================
|
|
@@ -108,6 +109,12 @@ export interface Ei_Interface {
|
|
|
108
109
|
onToolAdded?: () => void;
|
|
109
110
|
onToolUpdated?: (id: string) => void;
|
|
110
111
|
onToolRemoved?: () => void;
|
|
112
|
+
onRoomAdded?: () => void;
|
|
113
|
+
onRoomRemoved?: () => void;
|
|
114
|
+
onRoomUpdated?: (roomId: string) => void;
|
|
115
|
+
onRoomMessageAdded?: (roomId: string) => void;
|
|
116
|
+
onRoomMessageQueued?: (roomId: string) => void;
|
|
117
|
+
onRoomMessageProcessing?: (roomId: string) => void;
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
// =============================================================================
|
|
@@ -137,7 +144,8 @@ export interface StorageState {
|
|
|
137
144
|
messages: Message[];
|
|
138
145
|
}
|
|
139
146
|
>;
|
|
147
|
+
rooms?: Record<string, import("./rooms.js").RoomEntity>;
|
|
140
148
|
queue: LLMRequest[];
|
|
141
|
-
providers: ToolProvider[];
|
|
142
|
-
tools: ToolDefinition[];
|
|
149
|
+
providers: ToolProvider[];
|
|
150
|
+
tools: ToolDefinition[];
|
|
143
151
|
}
|
package/src/core/types/llm.ts
CHANGED
|
@@ -23,6 +23,9 @@ export interface Message {
|
|
|
23
23
|
e?: boolean; // Event (epic) extraction completed
|
|
24
24
|
// Image generation fields (web-only, ephemeral)
|
|
25
25
|
_synthesis?: boolean; // True if message was created by multi-message synthesis
|
|
26
|
+
speaker_name?: string; // Display name of actual speaker; set on room messages for clean hydration
|
|
27
|
+
|
|
28
|
+
external?: boolean; // Set by integration importers (OpenCode, Cursor, Claude Code); invisible to LLM context
|
|
26
29
|
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EI V1 Room Types
|
|
3
|
+
* Source of truth: CONTRACTS.md
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RoomMode, ContextStatus } from "./enums.js";
|
|
7
|
+
|
|
8
|
+
export interface RoomMessage {
|
|
9
|
+
id: string;
|
|
10
|
+
parent_id: string | null;
|
|
11
|
+
role: "human" | "persona";
|
|
12
|
+
persona_id?: string;
|
|
13
|
+
verbal_response?: string;
|
|
14
|
+
action_response?: string;
|
|
15
|
+
silence_reason?: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
read: boolean;
|
|
18
|
+
context_status: ContextStatus;
|
|
19
|
+
|
|
20
|
+
f?: boolean;
|
|
21
|
+
t?: boolean;
|
|
22
|
+
p?: boolean;
|
|
23
|
+
e?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RoomEntity {
|
|
27
|
+
id: string;
|
|
28
|
+
display_name: string;
|
|
29
|
+
entity: "room";
|
|
30
|
+
mode: RoomMode;
|
|
31
|
+
persona_ids: string[];
|
|
32
|
+
judge_persona_id?: string;
|
|
33
|
+
active_node_id: string | null;
|
|
34
|
+
is_archived: boolean;
|
|
35
|
+
created_at: string;
|
|
36
|
+
last_updated: string;
|
|
37
|
+
last_activity: string;
|
|
38
|
+
capture_used?: boolean;
|
|
39
|
+
messages: RoomMessage[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RoomCreationInput {
|
|
43
|
+
display_name: string;
|
|
44
|
+
mode: RoomMode;
|
|
45
|
+
persona_ids: string[];
|
|
46
|
+
judge_persona_id?: string;
|
|
47
|
+
initial_message: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface RoomSummary {
|
|
51
|
+
id: string;
|
|
52
|
+
display_name: string;
|
|
53
|
+
mode: RoomMode;
|
|
54
|
+
persona_ids: string[];
|
|
55
|
+
active_node_id: string | null;
|
|
56
|
+
is_archived: boolean;
|
|
57
|
+
last_activity: string;
|
|
58
|
+
unread_count: number;
|
|
59
|
+
}
|
package/src/core/types.ts
CHANGED
package/src/core/utils/decay.ts
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Ported from V0: v0/src/topic-decay.ts
|
|
2
|
+
* Exponential decay utility for exposure values.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Formula: v(t) = v₀ · e^(-K · days)
|
|
5
|
+
*
|
|
6
|
+
* K=0.1 means ~9.5% decay per day regardless of current value.
|
|
7
|
+
* Decays fastest immediately after peak, slows as it approaches 0.
|
|
8
|
+
* A topic at 1.0 reaches ~0.5 after ~7 days, ~0.05 after ~30 days.
|
|
9
|
+
*
|
|
10
|
+
* This replaced the old logistic approximation (K * v * (1-v) * hours)
|
|
11
|
+
* which had the wrong shape: it decayed FASTEST at 0.5, not at 1.0,
|
|
12
|
+
* and was aggressive enough to drop 0.2 → 0 in a single day.
|
|
7
13
|
*/
|
|
8
14
|
|
|
9
|
-
export function
|
|
15
|
+
export function calculateExponentialDecay(
|
|
10
16
|
currentValue: number,
|
|
11
17
|
hoursSinceUpdate: number,
|
|
12
18
|
K: number = 0.1
|
|
13
19
|
): number {
|
|
14
|
-
const
|
|
15
|
-
return Math.max(0, Math.min(1, currentValue -
|
|
20
|
+
const days = hoursSinceUpdate / 24;
|
|
21
|
+
return Math.max(0, Math.min(1, currentValue * Math.exp(-K * days)));
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
export function applyDecayToValue(
|
|
@@ -28,6 +34,6 @@ export function applyDecayToValue(
|
|
|
28
34
|
return { newValue: currentValue, hoursSinceUpdate };
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
const newValue =
|
|
37
|
+
const newValue = calculateExponentialDecay(currentValue, hoursSinceUpdate, K);
|
|
32
38
|
return { newValue, hoursSinceUpdate };
|
|
33
39
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ExposureImpact } from "../../prompts/human/types.js";
|
|
2
|
+
|
|
3
|
+
export function calculateExposureCurrent(impact: ExposureImpact | undefined, current: number = 0): number {
|
|
4
|
+
const target = (() => {
|
|
5
|
+
switch (impact) {
|
|
6
|
+
case "high": return 0.9;
|
|
7
|
+
case "medium": return 0.6;
|
|
8
|
+
case "low": return 0.3;
|
|
9
|
+
case "none": return 0.1;
|
|
10
|
+
default: return 0.5;
|
|
11
|
+
}
|
|
12
|
+
})();
|
|
13
|
+
return Math.max(target, current);
|
|
14
|
+
}
|